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
def reconfig(self, data): self.interval = int(data.get('interval', 60)) if self.loghandler: logger.removeHandler(self.loghandler) fname = data.get('logfile', '/var/log/sauron.log') # Set up the logging file self.loghandler = logging.FileHandler(fname, mode='a') self.loghandler.setFormatter(formatter) self.loghandler.setLevel(logging.INFO) logger.addHandler(self.loghandler) # Read in /all/ the metrics! try: if len(data['metrics']) == 0: logger.error('No metrics in config file!') exit(1) for key,value in data['metrics'].items(): try: try: d = dict(value.items()) self.metrics[key].reconfig(**d) except: module = value['module'] m = __import__('sauron.metrics.%s' % module) m = getattr(m, 'metrics') m = getattr(m, module) c = getattr(m, module) del d['module'] d['name'] = key self.metrics[key] = c(**d) except KeyError: logger.exception('No module listed for metric %s' % key) exit(1) except ImportError: logger.exception('Unable to import module %s' % module) exit(1) except TypeError as e: logger.exception('Unable to initialize metric %s' % key) exit(1) except MetricException as e: logger.exception('Module Exception %s' % module) exit(1) except KeyError: logger.error('No metrics in config file!') exit(1) # Read in /all/ the emitters! try: if self.dryrun: logger.warn('Skipping all emitters because of --dry-run') self.emitters[''] = Emitter() return if len(data['emitters']) == 0: logger.error('No metrics in config file!') exit(1) for key,value in data['emitters'].items(): try: m = __import__('sauron.emitters.%s' % key) m = getattr(m, 'emitters') m = getattr(m, key) c = getattr(m, key) d = dict(value.items()) self.emitters[key] = c(**d) except ImportError: logger.exception('Unable to import module %s' % key) exit(1) except TypeError as e: logger.exception('Unable to initialize emitter %s' % key) exit(1) except EmitterException as e: logger.exception('Error with module %s' % module) exit(1) except: logger.exception('Emitter error!') exit(1)
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
def execute_request(self, request, *args, **kwargs): """ This is the entry point for all incoming requests (To be more precise, the URL mapper calls the L{resource.Resource.__call__} that does some pre-processing, which then calls L{execute_request} ) It guides the request through all the necessary steps up to the point that its result is serialized into a dictionary. @type request: HTTPRequest object @param request: Incoming request @rype: dict @return: Dictionary of the result. The dictionary contains the following keys: * data: Contains the result of running the requested operation. The value to this key can be a dictionary, list, or string. Within this data structure, any dictionaries or lists are made of strings, with the exception of dates, which appear as I{datetime} functions, and will be serialized by the JSONEmitter. * total: Is only included if slicing was performed, and indicates the total result size. * Any other key can be included, if L{BaseHandler.enrich_response} has been overridden """ # Validate request body data if hasattr(request, 'data') and request.data is not None: if request.method.upper() == 'PUT': # In the case of PUT requests, we first force the evaluation of # the affected dataset (theferore if there are any # HttpResourceGone exceptions, they will be raised now), and then in the # ``validate`` method, we perform any data validations. We # assign it to parameter ``request.dataset``. request.dataset = self.data(request, *args, **kwargs) self.validate(request, *args, **kwargs) # Pick action to run action = getattr(self, CALLMAP.get(request.method.upper())) # Run it data = action(request, *args, **kwargs) # Select output fields fields = self.get_output_fields(request) # Slice sliced_data, total = self.response_slice_data(request, data) # inject fake dynamic fields to the response data sliced_data = self.inject_fake_dynamic_fields(request, sliced_data, fields) # Use the emitter to serialize any python objects / data structures # within I{sliced_data}, to serializable forms(dict, list, string), # so that the specific emitter we use for returning # the response, can easily serialize them in some other format, # The L{Emitter} is responsible for making sure that only fields contained in # I{fields} will be included in the result. emitter = Emitter(self, sliced_data, fields) ser_data = emitter.construct() # Structure the response data ret = {'data': ser_data} if total is not None: ret['total'] = total # Add extra metadata self.enrich_response(ret, data) if request.method.upper() == 'DELETE': self.data_safe_for_delete(data) return ret