Example #1
0
    def cast_value(self, field, value):
        if isinstance(field, ResourceListField):
            if not isinstance(value, dict):
                raise APIError.BadRequest(
                    "Expected nested object in list for %s" % field)
            try:
                resource = field.resource_type()
                if isinstance(resource, DebugResource):
                    return value.copy()
                new_object = {}
                for k, v in value.iteritems():
                    if not k in resource._fields and self.ignore_missing_fields:
                        new_object[k] = v
                        continue
                    _field = resource._fields[k]
                    if _field.property_name is not None:
                        k = _field.property_name
                    new_object[k] = self.cast_value(_field, v)

                if not getattr(resource, "_underlying_model", None):
                    return new_object

                return resource._underlying_model(**new_object)
            except Exception:
                logging.exception(
                    "Failed to cast value to _underlying_model of resource_type: %s"
                    % getattr(field, 'resource_type', None))
                raise

        # TODO: Use field to cast field back into a value,
        # if possible.
        return value
Example #2
0
    def apply_to_model(self, dt):
        for k, v in self.patch.iteritems():
            field = self.get_field_from_resource(k)
            if field is None:
                setattr(dt, k, v)
            elif isinstance(v, dict):
                # Recursive application.
                resource = self.get_resource_from_field(field)
                patch = ResourcePatch(
                    v,
                    resource,
                    ignore_missing_fields=self.ignore_missing_fields)
                patch.apply_to_model(getattr(dt, k, None))
            elif isinstance(v, list):
                if (not isinstance(field, ResourceListField)
                        and not isinstance(field, ListField)):
                    raise APIError.BadRequest(
                        "List not expected for field '%s'" % k)
                new_list = []
                for itm in v:
                    new_list.append(self.cast_value(field, itm))

                setattr(dt, k, new_list)
            else:
                # Cast value and set
                setattr(dt, k, self.cast_value(field, v))
Example #3
0
    def _handle(self, context):
        resource = getattr(self, "_resource", None)
        if not isinstance(resource, Resource):
            raise NotImplementedError(
                "%s needs to define _resource: Resource which will be patched"
                % self.__class__.__name__)

        if (context.headers.get('Content-Type').lower() !=
                self.MERGE_CONTENT_TYPE):
            raise APIError.UnsupportedMedia("PATCH expects content-type %r" %
                                            self.MERGE_CONTENT_TYPE)

        try:
            patch = ResourcePatch(patch=json.loads(context.body),
                                  resource=resource)
        except Exception, exc:
            raise APIError.UnprocessableEntity(
                "Could not decode JSON from request payload: %s" % exc)
Example #4
0
    def get_field_from_resource(self, field):
        if isinstance(self.resource, DebugResource):
            # no fields defined in a DebugResource
            return None

        try:
            return self.resource._fields[field]
        except KeyError:
            if not self.ignore_missing_fields:
                raise APIError.BadRequest("Field '%s' is not expected." %
                                          field)

            return None
Example #5
0
    def _execute(self, request, **kwargs):
        """The top-level execute function for the endpoint.  This method is
        intended to remain as-is, and not be overridden.  It gets called by
        your HTTP framework's route handler, and performs the following actions
        to process the request:

        ``authenticate_request``
            Validate the Bearer token, populate the ``current_user``, and make
            sure that the token covers the scope needed to call the requested
            method.
                         *
                         *
        ``parse arguments``
            The argument parser is responsible for:
              - First, coercing and patching any parameters that might require
                it due to versioning (i.e. the caller is using an old API
                version that supports `index` as a parameter for pagination,
                but the current version uses the name `offset`)
              - Second, iterating through the endpoint's supported arguments
                and validating that the params passed in comply with the
                endpoint's requirements
              - Third, populating the `context.args` array with the validated
                arguments
            If any of the arguments are invalid, then the Argument parser will
            raise an ArgumentError that bubbles up to the `try/catch` block of
            the execute method.
                         *
                         *
        ``before handler``
            The before_handlers are specified by the Endpoint definition, and
            are intended to supporty DRY-ing up your codebase.  Have a set of
            Endpoints that all need to grab an object from the ORM based on the
            same parameter?  Make them inherit from an Endpoint subclass that
            performs that task in a before_handler!
                         *
                         *
        ``handle``
            The core logic of your API endpoint, as implemented by you in your
            Endpoint subclass.  The API Framework expects ``handle`` to return
            a dictionary specifying the response object and the JSON key that
            it should hang off of, or a tuple of a dictionary and an HTTP status
            code.
                         *
                         *
        ``after_handler``
            Like the before_handlers, the ``after_handlers`` happen after the
            handle method, and allow the endpoint developer to re-use code for
            post-processing data from an endpoint.
                         *
                         *
        ``render response``
            Like the argument parser, the response renderer is responsible for
            a few things:
              - First, it converts the ORM objects into JSON-serializable
                Python dictionaries using the Resource objects defined by the
                API implementation,
              - Second, it does any version parameter coersion, renaming and
                reformatting the edge version of the response to match the
                version requested by the API caller,
              - and Third, it serializes the Python dictionary into the response
                format requested by the API caller (right now, we only support
                JSON responses, but it'd be reasonble to support something like
                HTML or XML or whatever in the future).
            The rendered JSON text is then returned as the response that should
            be sent by your HTTP framework's routing handler.
                         *
                         *
        ``_after_response_handler``
            The `_after_response_handlers` are sepcified by the Endpoint
            definition, and enable manipulation of the response object before it
            is returned to the client, but after the response is rendered.

            Because these are instancemethods, they may share instance data
            from `self` specified in the endpoint's `_handle` method.

        ``_allow_cors``
            This value is set to enable CORs for a given endpoint.

            When set to a string it supplies an explicit value to
            'Access-Control-Allow-Origin'.

            Set to True, this will allow access from *all* domains;
                Access-Control-Allow-Origin = "*"

        """
        try:
            self._create_context(request)
            self._authenticate()

            self._parse_args()

            if hasattr(self, '_before_handlers') and \
                    isinstance(self._before_handlers, (list, tuple)):
                for handler in self._before_handlers:
                    handler(self._context)

            self._context.handler_result = self._handle(self._context)

            if hasattr(self, '_after_handlers') and \
                    isinstance(self._after_handlers, (list, tuple)):
                for handler in self._after_handlers:
                    handler(self._context)

            self._render()
            response = self._context.response
            # After calling ._render(), the response is ready to go, so we
            # shouldn't need to handle any other exceptions beyond this point.
        except AuthenticationError as e:
            if hasattr(e, 'message') and e.message is not None:
                message = e.message
            else:
                message = "You don't have permission to do that."
            err = APIError.Forbidden(message)
            response = self._response_class(*err.response)
            response.headers["Content-Type"] = 'application/json'
        except ArgumentError as e:
            err = APIError.UnprocessableEntity(e.message)
            response = self._response_class(*err.response)
            response.headers["Content-Type"] = 'application/json'
        except APIError as e:
            response = self._response_class(*e.response)
            response.headers["Content-Type"] = 'application/json'
        except PaleRaisedResponse as r:
            response = self._response_class(*r.response)
            response.headers["Content-Type"] = 'application/json'
        except Exception as e:
            logging.exception(e)
            raise

        allow_cors = getattr(self, "_allow_cors", None)
        if allow_cors is True:
            response.headers['Access-Control-Allow-Origin'] = '*'
        elif isinstance(allow_cors, basestring):
            response.headers['Access-Control-Allow-Origin'] = allow_cors

        try:
            if hasattr(self, '_after_response_handlers') and \
                    isinstance(self._after_response_handlers, (list, tuple)):
                for handler in self._after_response_handlers:
                    handler(self._context, response)
        except Exception as e:
            logging.exception(
                "Failed to process _after_response_handlers for Endpoint %s" %
                self.__class__.__name__)
            raise

        return response