Commits

Juergen Brendel  committed d8dcbd5

Initial handling of the accept header.

Specifying the format on the URI (with the 'format' argument) will
override whatever is found in the accept header.

The content type of the Emitter is used to match against whatever
is provided in the accept header. If multiple Emitters register with
the same content type then whichever registered first will be the
chosen Emitter.

  • Participants
  • Parent commits 4fe8af1

Comments (0)

Files changed (3)

 Rock Howard for improving form validation behavior
 Jim Robert for contributing a fix for #117
 Emanuele Rocca for improving error handling
+Juergen Brendel for handling of the accept header

File piston/emitters.py

     as the methods on the handler. Issue58 says that's no good.
     """
     EMITTERS = { }
+    EMITTERS_ACCEPT_HDR_FORMAT_MAP = { }
+    EMITTERS_FORMAT_ACCEPT_HDR_MAP = { }
     RESERVED_FIELDS = set([ 'read', 'update', 'create',
                             'delete', 'model', 'anonymous',
                             'allowed_methods', 'fields', 'exclude' ])
         yield self.render(request)
 
     @classmethod
+    def get_emitter_name_by_accept_hdr(cls, accept_hdr):
+        """
+        Look at the accept header and return the name of a suitable emitter.
+
+        Return None if no match could be found.
+
+        """
+        return cls.EMITTERS_ACCEPT_HDR_FORMAT_MAP.get(accept_hdr)
+
+    @classmethod
     def get(cls, format):
         """
         Gets an emitter, returns the class and a content-type.
          - `content_type`: The content type to serve response as.
         """
         cls.EMITTERS[name] = (klass, content_type)
+        # Lots of info can be given in the content_type, such a encodings
+        # or quality.
+        # We are ignoring those for now (even though they really should
+        # also be matched to the accept header). We strip off all arguments.
+        elems = content_type.split(";")
+        ct    = elems[0].strip()
+        if ct not in cls.EMITTERS_ACCEPT_HDR_FORMAT_MAP:
+            # We only register an emitter for one accept header value. Whichever
+            # was registered first with a given content-type is the one used when
+            # the accept header matches.
+            cls.EMITTERS_ACCEPT_HDR_FORMAT_MAP[ct] = name
+            cls.EMITTERS_FORMAT_ACCEPT_HDR_MAP[name] = ct  # Also remember for quick reverse lookup
 
     @classmethod
     def unregister(cls, name):
         Remove an emitter from the registry. Useful if you don't
         want to provide output in one of the built-in emitters.
         """
+        if name in cls.EMITTERS_FORMAT_ACCEPT_HDR_MAP:
+            cls.EMITTERS_ACCEPT_HDR_FORMAT_MAP.pop(cls.EMITTERS_FORMAT_ACCEPT_HDR_MAP[name])
+            cls.EMITTERS_FORMAT_ACCEPT_HDR_MAP.pop(name)
         return cls.EMITTERS.pop(name, None)
 
 class XMLEmitter(Emitter):

File piston/resource.py

         em = kwargs.pop('emitter_format', None)
 
         if not em:
-            em = request.GET.get('format', 'json')
+            em = request.GET.get('format')
+
+        if not em:
+            # Nothing was sent explicitly in the URI, so we can now
+            # start to examine the accept header.
+            accept = request.META.get("HTTP_ACCEPT")
+            if accept:
+                em = Emitter.get_emitter_name_by_accept_hdr(accept)
+
+        if not em:
+            # After all of this we still don't know? Use json as fallback...
+            em = 'json'
 
         return em