def serialize_result(self, result, request, emitter_format): """ The request has been executed succefully and we end up here to serialize the result @type result: dict @param result: Result of the execution of the handler, wrapped in a dictionary. The dictionary contains the I{data} key, whose value is the result of running the operation. The value can be another dictionary, list or simply a string. If it's a dictionary or list, it might contain other dictionaries/lists, strings, or even I{datetime} functions. All dictionary key/value pairs, and list elements, should be strings, other than the I{datetime} functions. In anycase, I{self.data} should be serializable very easily, without any magic. @type request: HTTPRequest @param request: Incoming request @type emitter_format: str @param emitter_format: Emitter format """ # Find the Emitter class, and the corresponding content # type emitter_class, mimetype = Emitter.get(emitter_format) # create instance of the emitter class serializer = emitter_class(self.handler, result, None) # serialize the result serialized_result = serializer.render(request) return serialized_result, mimetype, emitter_format
def __call__(self, request, *args, **kwargs): # Support emitter both through (?P<emitter_format>) and ?format=emitter. em_format = self.determine_emitter(request, *args, **kwargs) kwargs.pop('emitter_format', None) response = self.response_class() try: emitter, ct = Emitter.get(em_format) except ValueError: raise PistonBadRequestException("Invalid output format specified '%s'." % em_format) if not response.error_message: meth = None try: handler, meth, fields, anonymous = self.process_request(request, response, *args, **kwargs) except Exception, e: handler, meth, fields, anonymous = None, None, (), False self.error_handler(response, e, request, meth)
def __call__(self, request, *args, **kwargs): # Support emitter both through (?P<emitter_format>) and ?format=emitter. em_format = self.determine_emitter(request, *args, **kwargs) kwargs.pop('emitter_format', None) response = self.response_class() try: emitter, ct = Emitter.get(em_format) except ValueError: raise PistonBadRequestException( "Invalid output format specified '%s'." % em_format) if not response.error_message: meth = None try: handler, meth, fields, anonymous = self.process_request( request, response, *args, **kwargs) except Exception, e: handler, meth, fields, anonymous = None, None, (), False self.error_handler(response, e, request, meth)
def form_validation_response(self, request, e, em_format): """ Method to return form validation error information. You will probably want to override this in your own `Resource` subclass. """ try: emitter, ct = Emitter.get(em_format) fields = self.handler.fields except ValueError: result = rc.BAD_REQUEST result.content = "Invalid output format specified '%s'." % em_format return result serialized_errors = dict( (key, [unicode(v) for v in values]) for key,values in e.form.errors.items() ) srl = emitter(serialized_errors, typemapper, self.handler, fields, False) stream = srl.render(request) resp = HttpResponse(stream, mimetype=ct, status=400) return resp
class Resource(object): """ Resource. Create one for your URL mappings, just like you would with Django. Takes one argument, the handler. The second argument is optional, and is an authentication handler. If not specified, `NoAuthentication` will be used by default. """ callmap = { 'GET': 'read', 'POST': 'create', 'PUT': 'update', 'DELETE': 'delete' } def __init__(self, handler, authentication=None): if not callable(handler): raise AttributeError("Handler not callable.") self.handler = handler() self.csrf_exempt = getattr(self.handler, 'csrf_exempt', True) if not authentication: self.authentication = (NoAuthentication(), ) elif isinstance(authentication, (list, tuple)): self.authentication = authentication else: self.authentication = (authentication, ) # Erroring self.email_errors = getattr(settings, 'PISTON_EMAIL_ERRORS', True) self.display_errors = getattr(settings, 'PISTON_DISPLAY_ERRORS', True) self.stream = getattr(settings, 'PISTON_STREAM_OUTPUT', False) def determine_emitter(self, request, *args, **kwargs): """ Function for determening which emitter to use for output. It lives here so you can easily subclass `Resource` in order to change how emission is detected. You could also check for the `Accept` HTTP header here, since that pretty much makes sense. Refer to `Mimer` for that as well. """ em = kwargs.pop('emitter_format', None) if not em: em = request.GET.get('format', 'json') return em def form_validation_response(self, e): """ Method to return form validation error information. You will probably want to override this in your own `Resource` subclass. """ resp = rc.BAD_REQUEST resp.write(u' ' + unicode(e.form.errors)) return resp @property def anonymous(self): """ Gets the anonymous handler. Also tries to grab a class if the `anonymous` value is a string, so that we can define anonymous handlers that aren't defined yet (like, when you're subclassing your basehandler into an anonymous one.) """ if hasattr(self.handler, 'anonymous'): anon = self.handler.anonymous if callable(anon): return anon for klass in typemapper.keys(): if anon == klass.__name__: return klass return None def authenticate(self, request, rm): actor, anonymous = False, True for authenticator in self.authentication: if not authenticator.is_authenticated(request): if self.anonymous and \ rm in self.anonymous.allowed_methods: actor, anonymous = self.anonymous(), True else: actor, anonymous = authenticator.challenge, CHALLENGE else: return self.handler, self.handler.is_anonymous return actor, anonymous @vary_on_headers('Authorization') def __call__(self, request, *args, **kwargs): """ NB: Sends a `Vary` header so we don't cache requests that are different (OAuth stuff in `Authorization` header.) """ rm = request.method.upper() # Django's internal mechanism doesn't pick up # PUT request, so we trick it a little here. if rm == "PUT": coerce_put_post(request) actor, anonymous = self.authenticate(request, rm) if anonymous is CHALLENGE: return actor() else: handler = actor # Allow for emulated PUT requests -- http://backbonejs.org/#Sync-emulateHTTP if 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META: rm = request.META.get('HTTP_X_HTTP_METHOD_OVERRIDE') if rm == "PUT": request.PUT = request.POST # Translate nested datastructs into `request.data` here. if rm in ('POST', 'PUT'): try: translate_mime(request) except MimerDataException: return rc.BAD_REQUEST if not hasattr(request, 'data'): if rm == 'POST': request.data = request.POST else: request.data = request.PUT if not rm in handler.allowed_methods: return HttpResponseNotAllowed(handler.allowed_methods) meth = getattr(handler, self.callmap.get(rm, ''), None) if not meth: raise Http404 # Support emitter both through (?P<emitter_format>) and ?format=emitter. em_format = self.determine_emitter(request, *args, **kwargs) kwargs.pop('emitter_format', None) # Clean up the request object a bit, since we might # very well have `oauth_`-headers in there, and we # don't want to pass these along to the handler. request = self.cleanup_request(request) try: result = meth(request, *args, **kwargs) except Exception, e: result = self.error_handler(e, request, meth, em_format) try: emitter, ct = Emitter.get(em_format) fields = handler.fields if hasattr(handler, 'list_fields') and isinstance( result, (list, tuple, QuerySet, RawQuerySet)): fields = handler.list_fields except ValueError: result = rc.BAD_REQUEST result.content = "Invalid output format specified '%s'." % em_format return result status_code = 200 # If we're looking at a response object which contains non-string # content, then assume we should use the emitter to format that # content if self._use_emitter(result): status_code = result.status_code # Note: We can't use result.content here because that # method attempts to convert the content into a string # which we don't want. when # _is_string/_base_content_is_iter is False _container is # the raw data result = result._container srl = emitter(result, typemapper, handler, fields, anonymous) try: """ Decide whether or not we want a generator here, or we just want to buffer up the entire result before sending it to the client. Won't matter for smaller datasets, but larger will have an impact. """ if self.stream: stream = srl.stream_render(request) else: stream = srl.render(request) if not isinstance(stream, HttpResponse): resp = HttpResponse(stream, content_type=ct, status=status_code) else: resp = stream resp.streaming = self.stream return resp except HttpStatusCode, e: return e.response
assert range_start != None assert range_end != None return (range_start, range_end) try: total = result.count() start, end = get_range(request_range[0], request_range[1], total) result = result[start:end + 1] content_range = "items %i-%i/%i" % (start, end, total) except BadRangeException, e: resp = rc.BAD_RANGE resp.write("\n%s" % e.value) return resp emitter, ct = Emitter.get(em_format) fields = handler.fields if hasattr(handler, 'list_fields') and (isinstance(result, list) or isinstance(result, QuerySet)): fields = handler.list_fields srl = emitter(result, typemapper, handler, fields, anonymous) try: """ Decide whether or not we want a generator here, or we just want to buffer up the entire result before sending it to the client. Won't matter for smaller datasets, but larger will have an impact. """ if self.stream: stream = srl.stream_render(request)
def __call__(self, request, *args, **kwargs): """ NB: Sends a `Vary` header so we don't cache requests that are different (OAuth stuff in `Authorization` header.) """ rm = request.method.upper() # Django's internal mechanism doesn't pick up # PUT request, so we trick it a little here. if rm == "PUT": coerce_put_post(request) actor, anonymous = self.authenticate(request, rm) if anonymous is CHALLENGE: return actor() else: handler = actor # Translate nested datastructs into `request.data` here. if rm in ('POST', 'PUT'): try: translate_mime(request) except MimerDataException: return rc.BAD_REQUEST if not hasattr(request, 'data'): if rm == 'POST': request.data = request.POST else: request.data = request.PUT if not rm in handler.allowed_methods: return HttpResponseNotAllowed(handler.allowed_methods) meth = getattr(handler, self.callmap.get(rm, ''), None) if not meth: raise Http404 # Support emitter through (?P<emitter_format>) and ?format=emitter # and lastly Accept: header processing em_format = self.determine_emitter(request, *args, **kwargs) if not em_format: request_has_accept = 'HTTP_ACCEPT' in request.META if request_has_accept and self.strict_accept: return rc.NOT_ACCEPTABLE em_format = self.default_emitter kwargs.pop('emitter_format', None) # Clean up the request object a bit, since we might # very well have `oauth_`-headers in there, and we # don't want to pass these along to the handler. request = self.cleanup_request(request) try: result = meth(request, *args, **kwargs) except Exception as e: result = self.error_handler(e, request, meth, em_format) try: emitter, ct = Emitter.get(em_format) fields = handler.fields if hasattr(handler, 'list_fields') and isinstance( result, (list, tuple, QuerySet)): fields = handler.list_fields except ValueError: result = rc.BAD_REQUEST result.content = "Invalid output format specified '%s'." % em_format return result status_code = 200 # If we're looking at a response object which contains non-string # content, then assume we should use the emitter to format that # content if self._use_emitter(result): status_code = result.status_code # Note: We can't use result.content here because that # method attempts to convert the content into a string # which we don't want. when # _is_string/_base_content_is_iter is False _container is # the raw data result = result._container srl = emitter(result, typemapper, handler, fields, anonymous) try: """ Decide whether or not we want a generator here, or we just want to buffer up the entire result before sending it to the client. Won't matter for smaller datasets, but larger will have an impact. """ if self.stream: stream = srl.stream_render(request) else: stream = srl.render(request) if not isinstance(stream, HttpResponse): resp = HttpResponse(stream, content_type=ct, status=status_code) else: resp = stream resp.streaming = self.stream return resp except HttpStatusCode as e: return e.response
receive a basic "500 Internal Server Error" message. """ exc_type, exc_value, tb = sys.exc_info() rep = ExceptionReporter(request, exc_type, exc_value, tb.tb_next) if self.email_errors: self.email_exception(rep) if self.display_errors: return HttpResponseServerError( format_error('\n'.join(rep.format_exception()))) else: raise # Return serialized data emitter, ct = Emitter.get(em_format) srl = emitter(result, recurse_level, typemapper, handler, handler.fields, anonymous) try: """ Decide whether or not we want a generator here, or we just want to buffer up the entire result before sending it to the client. Won't matter for smaller datasets, but larger will have an impact. """ if self.stream: stream = srl.stream_render(request) else: stream = srl.render(request) resp = HttpResponse(stream, mimetype=ct) resp.streaming = self.stream return resp
def __call__(self, request, *args, **kwargs): """ NB: Sends a `Vary` header so we don't cache requests that are different (OAuth stuff in `Authorization` header.) """ rm = request.method.upper() # Django's internal mechanism doesn't pick up # PUT request, so we trick it a little here. if rm == "PUT": coerce_put_post(request) actor, anonymous = self.authenticate(request, rm) if anonymous is CHALLENGE: return actor(request) else: handler = actor # Translate nested datastructs into `request.data` here. if rm in ('POST', 'PUT'): try: translate_mime(request) except MimerDataException: return rc.BAD_REQUEST if not hasattr(request, 'data'): if rm == 'POST': request.data = request.POST else: request.data = request.PUT if not rm in handler.allowed_methods: return HttpResponseNotAllowed(handler.allowed_methods) meth = getattr(handler, self.callmap.get(rm, ''), None) if not meth: raise Http404 # Support emitter both through (?P<emitter_format>) and ?format=emitter. em_format = self.determine_emitter(request, *args, **kwargs) kwargs.pop('emitter_format', None) # Clean up the request object a bit, since we might # very well have `oauth_`-headers in there, and we # don't want to pass these along to the handler. request = self.cleanup_request(request) result = meth(request, *args, **kwargs) try: emitter, ct = Emitter.get(em_format) fields = handler.fields if hasattr(handler, 'list_fields') and isinstance(result, (list, tuple, QuerySet)): fields = handler.list_fields except ValueError: result = rc.BAD_REQUEST result.content = "Invalid output format specified '%s'." % em_format return result status_code = 200 # If we're looking at a response object which contains non-string # content, then assume we should use the emitter to format that # content if isinstance(result, HttpResponse) and not result._is_string: status_code = result.status_code # Note: We can't use result.content here because that method attempts # to convert the content into a string which we don't want. # when _is_string is False _container is the raw data result = result._container srl = emitter(result, typemapper, handler, fields, anonymous) try: """ Decide whether or not we want a generator here, or we just want to buffer up the entire result before sending it to the client. Won't matter for smaller datasets, but larger will have an impact. """ if self.stream: stream = srl.stream_render(request) else: stream = srl.render(request) if not isinstance(stream, HttpResponse): resp = HttpResponse(stream, mimetype=ct, status=status_code) else: resp = stream resp.streaming = self.stream return resp except HttpStatusCode, e: return e.response
to the caller, so he can tell you what error they got. If `PISTON_DISPLAY_ERRORS` is not enabled, the caller will receive a basic "500 Internal Server Error" message. """ exc_type, exc_value, tb = sys.exc_info() rep = ExceptionReporter(request, exc_type, exc_value, tb.tb_next) if self.email_errors: self.email_exception(rep) if self.display_errors: return HttpResponseServerError( format_error('\n'.join(rep.format_exception()))) else: raise emitter, ct = Emitter.get(em_format) fields = handler.fields if hasattr(handler, 'list_fields') and ( isinstance(result, list) or isinstance(result, QuerySet)): fields = handler.list_fields srl = emitter(result, typemapper, handler, fields, anonymous) try: """ Decide whether or not we want a generator here, or we just want to buffer up the entire result before sending it to the client. Won't matter for smaller datasets, but larger will have an impact. """
class Resource(object): """ Resource. Create one for your URL mappings, just like you would with Django. Takes one argument, the handler. The second argument is optional, and is an authentication handler. If not specified, `NoAuthentication` will be used by default. """ callmap = { 'GET': 'read', 'POST': 'create', 'PUT': 'update', 'DELETE': 'delete' } def __init__(self, handler, authentication=None): if not callable(handler): raise AttributeError, "Handler not callable." self.handler = handler() self.csrf_exempt = getattr(self.handler, 'csrf_exempt', True) if not authentication: self.authentication = (NoAuthentication(), ) elif isinstance(authentication, (list, tuple)): self.authentication = authentication else: self.authentication = (authentication, ) # Erroring self.email_errors = getattr(settings, 'PISTON_EMAIL_ERRORS', True) self.display_errors = getattr(settings, 'PISTON_DISPLAY_ERRORS', True) self.stream = getattr(settings, 'PISTON_STREAM_OUTPUT', False) # Emitter selection self.strict_accept = getattr(settings, 'PISTON_STRICT_ACCEPT_HANDLING', False) self.default_emitter = getattr(settings, 'PISTON_DEFAULT_EMITTER', 'json') def determine_emitter(self, request, *args, **kwargs): """ Function for determening which emitter to use for output. It lives here so you can easily subclass `Resource` in order to change how emission is detected. """ try: return kwargs['emitter_format'] except KeyError: pass if 'format' in request.GET: return request.GET.get('format') if mimeparse and 'HTTP_ACCEPT' in request.META: supported_mime_types = set() emitter_map = {} for name, (klass, content_type) in Emitter.EMITTERS.items(): content_type_without_encoding = content_type.split(';')[0] supported_mime_types.add(content_type_without_encoding) emitter_map[content_type_without_encoding] = name preferred_content_type = mimeparse.best_match( list(supported_mime_types), request.META['HTTP_ACCEPT']) return emitter_map.get(preferred_content_type, None) def form_validation_response(self, e): """ Method to return form validation error information. You will probably want to override this in your own `Resource` subclass. """ resp = rc.BAD_REQUEST resp.write(u' ' + unicode(e.form.errors)) return resp @property def anonymous(self): """ Gets the anonymous handler. Also tries to grab a class if the `anonymous` value is a string, so that we can define anonymous handlers that aren't defined yet (like, when you're subclassing your basehandler into an anonymous one.) """ if hasattr(self.handler, 'anonymous'): anon = self.handler.anonymous if callable(anon): return anon for klass in typemapper.keys(): if anon == klass.__name__: return klass return None def authenticate(self, request, rm): actor, anonymous = False, True for authenticator in self.authentication: if not authenticator.is_authenticated(request): if self.anonymous and \ rm in self.anonymous.allowed_methods: actor, anonymous = self.anonymous(), True else: actor, anonymous = authenticator.challenge, CHALLENGE else: return self.handler, self.handler.is_anonymous return actor, anonymous @vary_on_headers('Authorization') def __call__(self, request, *args, **kwargs): """ NB: Sends a `Vary` header so we don't cache requests that are different (OAuth stuff in `Authorization` header.) """ rm = request.method.upper() # Django's internal mechanism doesn't pick up # PUT request, so we trick it a little here. if rm == "PUT": coerce_put_post(request) actor, anonymous = self.authenticate(request, rm) if anonymous is CHALLENGE: return actor() else: handler = actor # Translate nested datastructs into `request.data` here. if rm in ('POST', 'PUT'): try: translate_mime(request) except MimerDataException: return rc.BAD_REQUEST if not hasattr(request, 'data'): if rm == 'POST': request.data = request.POST else: request.data = request.PUT if not rm in handler.allowed_methods: return HttpResponseNotAllowed(handler.allowed_methods) meth = getattr(handler, self.callmap.get(rm, ''), None) if not meth: raise Http404 # Support emitter through (?P<emitter_format>) and ?format=emitter # and lastly Accept: header processing em_format = self.determine_emitter(request, *args, **kwargs) if not em_format: request_has_accept = 'HTTP_ACCEPT' in request.META if request_has_accept and self.strict_accept: return rc.NOT_ACCEPTABLE em_format = self.default_emitter kwargs.pop('emitter_format', None) # Clean up the request object a bit, since we might # very well have `oauth_`-headers in there, and we # don't want to pass these along to the handler. request = self.cleanup_request(request) try: result = meth(request, *args, **kwargs) except Exception, e: result = self.error_handler(e, request, meth, em_format) try: emitter, ct = Emitter.get(em_format) fields = handler.fields if hasattr(handler, 'list_fields') and isinstance( result, (list, tuple, QuerySet)): fields = handler.list_fields except ValueError: result = rc.BAD_REQUEST result.content = "Invalid output format specified '%s'." % em_format return result status_code = 200 # If we're looking at a response object which contains non-string # content, then assume we should use the emitter to format that # content if self._use_emitter(result): status_code = result.status_code result = result.content srl = emitter(result, typemapper, handler, fields, anonymous) try: """ Decide whether or not we want a generator here, or we just want to buffer up the entire result before sending it to the client. Won't matter for smaller datasets, but larger will have an impact. """ if self.stream: stream = srl.stream_render(request) else: stream = srl.render(request) if not isinstance(stream, HttpResponse): if django.VERSION >= (1, 7): resp = HttpResponse(stream, content_type=ct, status=status_code) else: resp = HttpResponse(stream, mimetype=ct, status=status_code) else: resp = stream resp.streaming = self.stream return resp except HttpStatusCode, e: return e.response