Commits

Anonymous committed f85951d

Refactored the Resource class to be able to specify custom exception handling logic.

Comments (0)

Files changed (2)

piston/emitters.py

     as the methods on the handler. Issue58 says that's no good.
     """
     EMITTERS = { }
-    RESERVED_FIELDS = set([ 'read', 'update', 'create', 
+    RESERVED_FIELDS = set([ 'read', 'update', 'create',
                             'delete', 'model', 'anonymous',
                             'allowed_methods', 'fields', 'exclude' ])
 
         self.handler = handler
         self.fields = fields
         self.anonymous = anonymous
-        
+
         if isinstance(self.data, Exception):
             raise
-    
+
     def method_fields(self, handler, fields):
         if not handler:
             return { }
 
         ret = dict()
-            
+
         for field in fields - Emitter.RESERVED_FIELDS:
             t = getattr(handler, str(field), None)
 
                 ret[field] = t
 
         return ret
-    
+
     def construct(self):
         """
         Recursively serialize a lot of types, and
         in cases where it doesn't recognize the type,
         it will fall back to Django's `smart_unicode`.
-        
+
         Returns `dict`.
         """
         def _any(thing, fields=()):
             Dispatch, all types are routed through here.
             """
             ret = None
-            
+
             if isinstance(thing, QuerySet):
                 ret = _qs(thing, fields=fields)
             elif isinstance(thing, (tuple, list, set)):
             Foreign keys.
             """
             return _any(getattr(data, field.name))
-        
+
         def _related(data, fields=()):
             """
             Foreign keys.
             """
             return [ _model(m, fields) for m in data.iterator() ]
-        
+
         def _m2m(data, field, fields=()):
             """
             Many to many (re-route to `_model`.)
             """
             return [ _model(m, fields) for m in getattr(data, field.name).iterator() ]
-        
+
         def _model(data, fields=()):
             """
             Models. Will respect the `fields` and/or
             ret = { }
             handler = self.in_typemapper(type(data), self.anonymous)
             get_absolute_uri = False
-            
+
             if handler or fields:
                 v = lambda f: getattr(data, f.attname)
 
 
                     if 'absolute_uri' in get_fields:
                         get_absolute_uri = True
-                
+
                     if not get_fields:
                         get_fields = set([ f.attname.replace("_id", "", 1)
                             for f in data._meta.fields + data._meta.virtual_fields])
-                
+
                     # sets can be negated.
                     for exclude in exclude_fields:
                         if isinstance(exclude, basestring):
                             get_fields.discard(exclude)
-                            
+
                         elif isinstance(exclude, re._pattern_type):
                             for field in get_fields.copy():
                                 if exclude.match(field):
                                     get_fields.discard(field)
-                                    
+
                 else:
                     get_fields = set(fields)
 
                 met_fields = self.method_fields(handler, get_fields)
-                           
+
                 for f in data._meta.local_fields + data._meta.virtual_fields:
                     if f.serialize and not any([ p in met_fields for p in [ f.attname, f.name ]]):
                         if not f.rel:
                             if f.attname[:-3] in get_fields:
                                 ret[f.name] = _fk(data, f)
                                 get_fields.remove(f.name)
-                
+
                 for mf in data._meta.many_to_many:
                     if mf.serialize and mf.attname not in met_fields:
                         if mf.attname in get_fields:
                             ret[mf.name] = _m2m(data, mf)
                             get_fields.remove(mf.name)
-                
+
                 # try to get the remainder of fields
                 for maybe_field in get_fields:
                     if isinstance(maybe_field, (list, tuple)):
                         # using different names.
                         ret[maybe_field] = _any(met_fields[maybe_field](data))
 
-                    else:                    
+                    else:
                         maybe = getattr(data, maybe_field, None)
                         if maybe:
                             if callable(maybe):
             else:
                 for f in data._meta.fields:
                     ret[f.attname] = _any(getattr(data, f.attname))
-                
+
                 fields = dir(data.__class__) + ret.keys()
                 add_ons = [k for k in dir(data) if k not in fields]
-                
+
                 for k in add_ons:
                     ret[k] = _any(getattr(data, k))
-            
+
             # resouce uri
             if self.in_typemapper(type(data), self.anonymous):
                 handler = self.in_typemapper(type(data), self.anonymous)
                         ret['resource_uri'] = reverser( lambda: (url_id, fields) )()
                     except NoReverseMatch, e:
                         pass
-            
+
             if hasattr(data, 'get_api_url') and 'resource_uri' not in ret:
                 try: ret['resource_uri'] = data.get_api_url()
                 except: pass
-            
+
             # absolute uri
             if hasattr(data, 'get_absolute_url') and get_absolute_uri:
                 try: ret['absolute_uri'] = data.get_absolute_url()
                 except: pass
-            
+
             return ret
-        
+
         def _qs(data, fields=()):
             """
             Querysets.
             """
             return [ _any(v, fields) for v in data ]
-                
+
         def _list(data):
             """
             Lists.
             """
             return [ _any(v) for v in data ]
-            
+
         def _dict(data, fields):
             """
             Dictionaries.
             """
             return dict([ (k, _any(v, fields)) for k, v in data.iteritems() ])
-            
+
         # Kickstart the seralizin'.
         return _any(self.data, self.fields)
-    
+
     def in_typemapper(self, model, anonymous):
         for klass, (km, is_anon) in self.typemapper.iteritems():
             if model is km and is_anon is anonymous:
                 return klass
-        
+
     def render(self):
         """
         This super emitter does not implement `render`,
         this is a job for the specific emitter below.
         """
         raise NotImplementedError("Please implement render.")
-        
+
     def stream_render(self, request, stream=True):
         """
         Tells our patched middleware not to look
         more memory friendly for large datasets.
         """
         yield self.render(request)
-        
+
     @classmethod
     def get(cls, format):
         """
             return cls.EMITTERS.get(format)
 
         raise ValueError("No emitters found for type %s" % format)
-    
+
     @classmethod
     def register(cls, name, klass, content_type='text/plain'):
         """
         Register an emitter.
-        
+
         Parameters::
          - `name`: The name of the emitter ('json', 'xml', 'yaml', ...)
          - `klass`: The emitter class.
          - `content_type`: The content type to serve response as.
         """
         cls.EMITTERS[name] = (klass, content_type)
-        
+
     @classmethod
     def unregister(cls, name):
         """
         want to provide output in one of the built-in emitters.
         """
         return cls.EMITTERS.pop(name, None)
-    
+
 class XMLEmitter(Emitter):
     def _to_xml(self, xml, data):
         if isinstance(data, (list, tuple)):
 
     def render(self, request):
         stream = StringIO.StringIO()
-        
+
         xml = SimplerXMLGenerator(stream, "utf-8")
         xml.startDocument()
         xml.startElement("response", {})
-        
+
         self._to_xml(xml, self.construct())
-        
+
         xml.endElement("response")
         xml.endDocument()
-        
+
         return stream.getvalue()
 
 Emitter.register('xml', XMLEmitter, 'text/xml; charset=utf-8')
             return '%s(%s)' % (cb, seria)
 
         return seria
-    
+
 Emitter.register('json', JSONEmitter, 'application/json; charset=utf-8')
 Mimer.register(simplejson.loads, ('application/json',))
-    
+
 class YAMLEmitter(Emitter):
     """
     YAML emitter, uses `safe_dump` to omit the
     """
     def render(self, request):
         return pickle.dumps(self.construct())
-        
+
 Emitter.register('pickle', PickleEmitter, 'application/python-pickle')
 
 """
             response = serializers.serialize(format, self.data, indent=True)
 
         return response
-        
+
 Emitter.register('django', DjangoEmitter, 'text/xml; charset=utf-8')

piston/resource.py

     is an authentication handler. If not specified,
     `NoAuthentication` will be used by default.
     """
-    callmap = { 'GET': 'read', 'POST': 'create', 
+    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()
-        
+
         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)
         that as well.
         """
         em = kwargs.pop('emitter_format', None)
-        
+
         if not em:
             em = request.GET.get('format', 'json')
 
         return em
-    
+
     @property
     def anonymous(self):
         """
         """
         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
 
                     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):
         """
             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
 
         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
         except TypeError, e:
             result = rc.BAD_REQUEST
             sig = hm.signature
 
             msg = 'Method signature does not match.\n\n'
-            
+
             if sig:
                 msg += 'Signature should be: %s' % sig
             else:
                 msg += 'Resource does not expect any parameters.'
 
-            if self.display_errors:                
+            if self.display_errors:
                 msg += '\n\nException was: %s' % str(e)
-                
+
             result.content = format_error(msg)
         except Http404:
             return rc.NOT_FOUND
             On errors (like code errors), we'd like to be able to
             give crash reports to both admins and also the calling
             user. There's two setting parameters for this:
-            
+
             Parameters::
              - `PISTON_EMAIL_ERRORS`: Will send a Django formatted
                error email to people in `settings.ADMINS`.
              - `PISTON_DISPLAY_ERRORS`: Will return a simple traceback
                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
+            return self.handle_exception(request, e, sys.exc_info(), em_format)
 
         emitter, ct = Emitter.get(em_format)
         try:
 
             if True in [ k.startswith("oauth_") for k in block.keys() ]:
                 sanitized = block.copy()
-                
+
                 for k in sanitized.keys():
                     if k.startswith("oauth_"):
                         sanitized.pop(k)
-                        
+
                 setattr(request, method_type, sanitized)
 
         return request
-        
-    # -- 
-    
+
+    # --
+
     def email_exception(self, reporter):
         subject = "Piston crash report"
         html = reporter.get_traceback_html()
         message = EmailMessage(settings.EMAIL_SUBJECT_PREFIX+subject,
                                 html, settings.SERVER_EMAIL,
                                 [ admin[1] for admin in settings.ADMINS ])
-        
+
         message.content_subtype = 'html'
         message.send(fail_silently=True)
+
+
+    def handle_exception(self, request, exc, exc_info, em_format):
+        exc_type, exc_value, tb = 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
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.