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, e: if e: r = rc.BAD_REQUEST r.content = e return r return rc.BAD_REQUEST if not hasattr(request, "data"): if rm == "POST": request.data = request.POST else: request.data = request.PUT
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 except UnsupportedMediaTypeException: return rc.UNSUPPORTED_MEDIA_TYPE 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)
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) if not self.authentication.is_authenticated(request): if hasattr(self.handler, 'anonymous') and \ callable(self.handler.anonymous) and \ rm in self.handler.anonymous.allowed_methods: handler = self.handler.anonymous() anonymous = True else: return self.authentication.challenge() else: handler = self.handler anonymous = handler.is_anonymous # Translate nested datastructs into `request.data` here. if rm in ('POST', 'PUT'): try: translate_mime(request) except MimerDataException, e: return rc.BAD_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) if not self.authentication.is_authenticated(request): if self.anonymous and \ rm in self.anonymous.allowed_methods: handler = self.anonymous() anonymous = True else: return self.authentication.challenge() else: handler = self.handler anonymous = handler.is_anonymous # Translate nested datastructs into `request.data` here. if rm in ('POST', 'PUT'): try: translate_mime(request) except MimerDataException: return rc.BAD_REQUEST # Handle 'tunneling' alternate method over POST if rm == "POST" and 'method' in request.POST: rm = request.POST['method'].upper() 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 FormValidationError, e: resp = rc.BAD_REQUEST resp.write(' '+str(e.form.errors)) 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() 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)
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.) """ self._request = request 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) if not self.authentication.is_authenticated(request): if hasattr(self.handler, 'anonymous') and \ callable(self.handler.anonymous) and \ rm in self.handler.anonymous.allowed_methods: handler = self.handler.anonymous() anonymous = True else: return self.authentication.challenge() else: handler = self.handler anonymous = handler.is_anonymous # Translate nested datastructs into `request.data` here. if rm in ('POST', 'PUT'): try: translate_mime(request) except MimerDataException: return rc.BAD_REQUEST 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 FormValidationError, e: # TODO: Use rc.BAD_REQUEST here return HttpResponse("Bad Request: %s" % e.form.errors, status=400)
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 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 FormValidationError, e: resp = rc.BAD_REQUEST resp.write(' ' + str(e.form.errors)) 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() 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
def handle(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) if not self.authentication.is_authenticated(request): if hasattr(self.handler, 'anonymous') and \ callable(self.handler.anonymous) and \ rm in self.handler.anonymous.allowed_methods: handler = self.handler.anonymous() anonymous = True else: return self.authentication.challenge() else: handler = self.handler anonymous = handler.is_anonymous # Translate nested datastructs into `request.data` here. if rm in ('POST', 'PUT'): try: translate_mime(request) except MimerDataException: return rc.BAD_REQUEST 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) # result is just html, handled in template # TODO: move this block into sites.py view handler if em_format == 'html': temp = get_template('fulcrum/resource_detail.html') ctxt = RequestContext(request, { 'resource': self, 'handler': self.handler }) return HttpResponse(temp.render(ctxt)) # Get recursion level recurse_level = self.get_recurse_level(request) # 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 is either a single object or a list of objects # something like... [<Blogpost: Sample test post 2>] result = meth(request, *args, **kwargs) except FormValidationError, e: # TODO: Use rc.BAD_REQUEST here return HttpResponse("Bad Request: %s" % e.form.errors, status=400)
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) if not self.authentication.is_authenticated(request): if self.anonymous and \ rm in self.anonymous.allowed_methods: handler = self.anonymous() anonymous = True else: return self.authentication.challenge() else: handler = self.handler anonymous = handler.is_anonymous # Translate nested datastructs into `request.data` here. if rm in ('POST', 'PUT'): try: translate_mime(request) except MimerDataException: return rc.BAD_REQUEST 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) def call_piston_precondition(meth, *args, **kwargs): if hasattr(meth, 'piston_precondition_decorator'): @meth.piston_precondition_decorator def fake_controller(request, *args, **kwargs): return rc.ALL_OK return fake_controller(request, *args, **kwargs) return rc.ALL_OK etag=None response = call_piston_precondition(meth, *args, **kwargs) if response.status_code != 200: return response try: result = meth(request, *args, **kwargs) # get new etag - it might have been changed tmp_r = call_piston_precondition(meth, *args, **kwargs) if tmp_r.has_header('ETag'): etag = tmp_r['ETag'] except FormValidationError, e: resp = rc.BAD_REQUEST resp.write(' '+str(e.form.errors)) 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() if rm == 'POST': block = getattr(request, 'POST', { }) # Support alternative request types via # _method parameter in POST requests if '_method' in block: rm = request.method = block['_method'].upper() sanitized = block.copy() sanitized.pop('_method') setattr(request, 'POST', sanitized) if rm == 'PUT': request.PUT = request.POST # Django's internal mechanism doesn't pick up # PUT request, so we trick it a little here. elif 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 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)
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
def process_request(self, request, response, *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 # on non POST request, so we trick it a little here. if rm in ( "PUT", "DELETE", ): coerce_put_post(request) actor, anonymous = self.authenticate(request, rm) result = None if anonymous is CHALLENGE: class ErrorHandler(BaseHandler): def error(self, *args, **kwargs): pass for func in self.callmap.values(): setattr(ErrorHandler, func, ErrorHandler.error) handler = ErrorHandler() meth = handler.error fields = () response.data = actor(request) else: handler = actor meth = None fields = () if not response.data: # Translate nested datastructs into `request.data` here. if rm in ( 'POST', 'PUT', 'DELETE', ): try: translate_mime(request) except MimerDataException: raise PistonBadRequestException( 'Error deserializing request data.') if not hasattr(request, 'data'): if rm == 'POST': request.data = request.POST elif rm == 'PUT': request.data = request.PUT elif rm == 'DELETE': request.data = request.DELETE if not request.data: # In the case where no data is provided, default to an empty # dictionary. Many serializers do not deal with empty string data # for deserialization. setattr(request, rm, {}) request.data = {} if not rm in handler.allowed_methods: raise PistonMethodException( headers={'Allow': handler.allowed_methods}) meth = getattr(handler, self.callmap.get(rm, ''), None) if not meth: raise PistonNotFoundException # 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) response.data = meth(request, *args, **kwargs) fields = handler.fields if hasattr(handler, 'list_fields') and isinstance( response.data, (list, tuple, QuerySet)): fields = handler.list_fields return handler, meth, fields, anonymous
def cleanup(self, request, *args, **kwargs): """ Cleanes up the incoming request, makes sure it's valid and allowed. In detail, the checks performed are the folowing: * If the request is I{PUT}, transform its data to I{POST}. * If request is I{PUT} or I{POST}, make sure the request body conforms to the I{Content-Type} header. * If request is I{PUT} with a list in the request body, raise exception. If request is I{Bulk-POST} and ``bulk_create`` has not been set, raise exception. * Makes sure that non-allowed incoming fields, are cut off the request body. """ request_method = request.method.upper() # Construct the request.data dictionary, if the request is PUT/POST if request_method in ('PUT', 'POST'): if request_method == 'PUT': # TODO: STUDY what this does exactly coerce_put_post(request) # Check whether data has the correct format, according to # ``Content-Type`` if request_method in ('PUT', 'POST'): try: translate_mime(request) except ValidationError: raise if not hasattr(request, 'data'): if request_method == 'POST': request.data = request.POST else: request.data = request.PUT if request.data is None: # In the case when Content-Type is not given or is invalid raise ValidationError('Please make sure the header '+ \ '"Content-Type: application/json" is given') # Bulk-PUT makes no sense at all, so it gives a ValidationError. # Bulk-POST should only be allowed if it has been explicitly enabled # by the ``bulk_create`` parameter. if request_method == 'PUT' and isinstance(request.data, list): raise ValidationError('Illegal Operation: PUT request with ' + \ 'array in request body') if request_method =='POST' and \ not self.handler.bulk_create and \ isinstance(request.data, list): raise ValidationError('API Handler does not allow bulk POST ' + \ 'requests') # Ok, the request has survived all the checks. At this point we strip # off the disallowed fields from the request body. if request_method in ('POST', 'PUT')\ and self.handler.allowed_in_fields != self.handler.ALL_FIELDS: if isinstance(request.data, list): # We assume it's a list of dictionaries, and reject any non dicts. new_request_data = [] for item in request.data: if not isinstance(item, dict): continue clean_item = dict((key, value) for key, value in item.iteritems() \ if key in self.handler.allowed_in_fields) new_request_data.append(clean_item) request.data = new_request_data else: # Assume it's a dictionary request.data = dict(( (key, value) for key, value in request.data.iteritems() \ if key in self.handler.allowed_in_fields))
def process_request(self, request, response, *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 # on non POST request, so we trick it a little here. if rm in ("PUT", "DELETE",): coerce_put_post(request) actor, anonymous = self.authenticate(request, rm) result = None if anonymous is CHALLENGE: class ErrorHandler(BaseHandler): def error(self, *args, **kwargs): pass for func in self.callmap.values(): setattr(ErrorHandler, func, ErrorHandler.error) handler = ErrorHandler() meth = handler.error fields = () response.data = actor(request) else: handler = actor meth = None fields = () if not response.data: # Translate nested datastructs into `request.data` here. if rm in ('POST', 'PUT', 'DELETE',): try: translate_mime(request) except MimerDataException: raise PistonBadRequestException('Error deserializing request data.') if not hasattr(request, 'data'): if rm == 'POST': request.data = request.POST elif rm == 'PUT': request.data = request.PUT elif rm == 'DELETE': request.data = request.DELETE if not request.data: # In the case where no data is provided, default to an empty # dictionary. Many serializers do not deal with empty string data # for deserialization. setattr(request, rm, {}) request.data = {} if not rm in handler.allowed_methods: raise PistonMethodException(headers={'Allow': handler.allowed_methods}) meth = getattr(handler, self.callmap.get(rm, ''), None) if not meth: raise PistonNotFoundException # 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) response.data = meth(request, *args, **kwargs) fields = handler.fields if hasattr(handler, 'list_fields') and isinstance(response.data, (list, tuple, QuerySet)): fields = handler.list_fields return handler, meth, fields, anonymous