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
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))
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)
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
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