Jesper Nøhr avatar Jesper Nøhr committed e1d7a29

decorator behavior, more handlers, adding crash report code, fork & mq handlers

Comments (0)

Files changed (5)

piston/authentication.py

         return resp
 
 def initialize_server_request(request):
-    """Shortcut for initialization."""
+    """
+    Shortcut for initialization.
+    """
     oauth_request = oauth.OAuthRequest.from_request(
         request.method, request.build_absolute_uri(), 
         headers=request.META, parameters=dict(request.REQUEST.items()),
     return oauth_server, oauth_request
 
 def send_oauth_error(err=None):
-    """Shortcut for sending an error."""
-    # send a 401 error
+    """
+    Shortcut for sending an error.
+    """
     response = HttpResponse(err.message.encode('utf-8'))
     response.status_code = 401
-    # return the authenticate header
+
     realm = 'Bitbucket.org OAuth'
     header = oauth.build_authenticate_header(realm=realm)
+
     for k, v in header.iteritems():
         response[k] = v
+
     return response
 
 def oauth_request_token(request):
     if oauth_server is None:
         return INVALID_PARAMS_RESPONSE
     try:
-        # create a request token
         token = oauth_server.fetch_request_token(oauth_request)
-        # return the token
+
         response = HttpResponse(token.to_string())
     except oauth.OAuthError, err:
         response = send_oauth_error(err)
+
     return response
 
 def oauth_auth_view(request, token, callback, params):
     if request.method == "GET":
         request.session['oauth'] = token.key
         params = oauth_request.get_normalized_parameters()
+
         oauth_view = getattr(settings, 'OAUTH_AUTH_VIEW', 'oauth_auth_view')
+
         return get_callable(oauth_view)(request, token, callback, params)
     elif request.method == "POST":
         if request.session.get('oauth', '') == token.key:
     except oauth.OAuthError, err:
         return send_oauth_error(err)
 
-def oauth_protected_area(request):
-    oauth_server, oauth_request = initialize_server_request(request)
-
-    try:
-        consumer, token, parameters = oauth_server.verify_request(oauth_request)
-        return HttpResponse("protected resource, consumer=%s, token=%s, parameters=%s, you are=%s" % (consumer, token, parameters, token.user))
-    except oauth.OAuthError, err:
-        return send_oauth_error(err)
-
-    return HttpResponse("OK = %s" % ok)
-                
 INVALID_PARAMS_RESPONSE = send_oauth_error(oauth.OAuthError('Invalid request parameters.'))
                 
 class OAuthAuthentication(object):
             'consumer_key', 'token', 'signature',
             'signature_method', 'timestamp', 'nonce' ] ]
         
-        is_in = lambda l: False not in [ (p in l) for p in must_have ]
+        is_in = lambda l: all([ (p in l) for p in must_have ])
 
         auth_params = request.META.get("HTTP_AUTHORIZATION", "")
         req_params = request.REQUEST
         args, _, _, defaults = inspect.getargspec(self.method)
 
         for idx, arg in enumerate(args):
-            if arg in ('self', 'request'):
+            if arg in ('self', 'request', 'form'):
                 continue
 
             didx = len(args)-idx

piston/emitters.py

 import types, decimal, yaml, types, re, inspect
+
 from django.db.models.query import QuerySet
 from django.db.models import Model, permalink
 from django.utils import simplejson
 from django.utils.encoding import smart_unicode
 from django.core.serializers.json import DateTimeAwareJSONEncoder
 from django.http import HttpResponse
+
 from utils import HttpStatusCode
 
 try:

piston/resource.py

+import sys, inspect
+
 from django.http import HttpResponse, Http404, HttpResponseNotAllowed, HttpResponseForbidden
+from django.views.debug import ExceptionReporter
 from django.views.decorators.vary import vary_on_headers
+from django.conf import settings
+from django.core.mail import send_mail, EmailMessage
+
 from emitters import Emitter
 from handler import typemapper
-from utils import coerce_put_post, FormValidationError, HttpStatusCode
+from doc import HandlerMethod
+from utils import coerce_put_post, FormValidationError, HttpStatusCode, rc, format_error
 
 class NoAuthentication(object):
     """
             self.authentication = NoAuthentication()
         else:
             self.authentication = authentication
+            
+        # Erroring
+        self.email_errors = getattr(settings, 'PISTON_EMAIL_ERRORS', True)
+        self.display_errors = getattr(settings, 'PISTON_DISPLAY_ERRORS', True)
     
     @vary_on_headers('Authorization')
     def __call__(self, request, *args, **kwargs):
             result = meth(request, *args, **kwargs)
         except FormValidationError, form:
             return HttpResponse("Bad Request: %s" % form.errors, status=400)
+        except TypeError, e:
+            result = rc.BAD_REQUEST
+            hm = HandlerMethod(meth)
+            sig = hm.get_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:                
+                msg += '\n\nException was: %s' % str(e)
+                
+            result.content = format_error(msg)
+        except HttpStatusCode, e:
+            result = e
         except Exception, e:
-            result = e
-        
+            """
+            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.
+            """
+            if self.email_errors:
+                exc_type, exc_value, tb = sys.exc_info()
+                rep = ExceptionReporter(request, exc_type, exc_value, tb.tb_next)
+
+                self.email_exception(rep)
+
+            if self.display_errors:
+                result = format_error('\n'.join(rep.format_exception()))
+            else:
+                raise
+
         emitter, ct = Emitter.get(request.GET.get('format', 'json'))
         srl = emitter(result, typemapper, handler, handler.fields)
 
         Removes `oauth_` keys from various dicts on the
         request object, and returns the sanitized version.
         """
-        for method_type in [ 'GET', 'PUT', 'POST', 'DELETE' ]:
+        for method_type in ('GET', 'PUT', 'POST', 'DELETE'):
             block = getattr(request, method_type, { })
 
             if True in [ k.startswith("oauth_") for k in block.keys() ]:
 
         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)
 from django.http import HttpResponseNotAllowed, HttpResponseForbidden, HttpResponse
 from django.core.urlresolvers import reverse
 from django.core.cache import cache
+from django import get_version as django_version
 from decorator import decorator
 
 from datetime import datetime, timedelta
 
+__version__ = '1.0'
+
+def get_version():
+    return __version__
+
+def format_error(error):
+    return u"Piston/%s (Django %s) crash report:\n\n%s" % \
+        (get_version(), django_version(), error)
+
 def create_reply(message, status=200):
     return HttpResponse(message, status=status)
 
 
 def validate(v_form, operation='POST'):
     @decorator
-    def dec(func):
-        def wrap(self, request, *a, **kwa):
-            form = v_form(getattr(request, operation))
+    def wrap(f, self, request, *a, **kwa):
+        form = v_form(getattr(request, operation))
+    
+        if form.is_valid():
+#            kwa.update({ 'form': form })
+            return f(self, request, *a, **kwa)
+        else:
+            raise FormValidationError(form)
+    return wrap
 
-            if form.is_valid():
-                kwa.update({ 'form': form })
-                return func(self, request, *a, **kwa)
-            else:
-                raise FormValidationError(form)
-                                
-        return wrap
-    return dec
-
-def throttle(max_requests, timeout=60*60):
+def throttle(max_requests, timeout=60*60, extra=''):
     """
     Simple throttling decorator, caches
     the amount of requests made in cache.
      - `timeout`: The timeout for the cache entry (default: 1 hour)
     """
     @decorator
-    def inner(f):
-        def wrap(self, request, *args, **kwargs):
-            if request.user.is_authenticated():
-                ident = request.user.username
-            else:
-                ident = request.META.get('REMOTE_ADDR', None)
-
-            if hasattr(request, 'throttle_extra'):
-                """
-                Since we want to be able to throttle on a per-
-                application basis, it's important that we realize
-                that `throttle_extra` might be set on the request
-                object. If so, append the identifier name with it.
-                """
-                ident += ':%s' % str(request.throttle_extra)
+    def wrap(f, self, request, *args, **kwargs):
+        if request.user.is_authenticated():
+            ident = request.user.username
+        else:
+            ident = request.META.get('REMOTE_ADDR', None)
+    
+        if hasattr(request, 'throttle_extra'):
+            """
+            Since we want to be able to throttle on a per-
+            application basis, it's important that we realize
+            that `throttle_extra` might be set on the request
+            object. If so, append the identifier name with it.
+            """
+            ident += ':%s' % str(request.throttle_extra)
+        
+        if ident:
+            """
+            Preferrably we'd use incr/decr here, since they're
+            atomic in memcached, but it's in django-trunk so we
+            can't use it yet. If someone sees this after it's in
+            stable, you can change it here.
+            """
+            ident += ':%s' % extra
+    
+            now = datetime.now()
+            ts_key = 'throttle:ts:%s' % ident
+            timestamp = cache.get(ts_key)
+            offset = now + timedelta(seconds=timeout)
+    
+            if timestamp and timestamp < offset:
+                t = rc.THROTTLED
+                wait = timeout - (offset-timestamp).seconds
+                t.content = 'Throttled, wait %d seconds.' % wait
+                
+                return t
+                
+            count = cache.get(ident, 1)
+            cache.set(ident, count+1)
             
-            if ident:
-                """
-                Preferrably we'd use incr/decr here, since they're
-                atomic in memcached, but it's in django-trunk so we
-                can't use it yet. If someone sees this after it's in
-                stable, you can change it here.
-                """
-                now = datetime.now()
-                ts_key = 'throttle:ts:%s' % ident
-                timestamp = cache.get(ts_key)
-                offset = now + timedelta(seconds=timeout)
-
-                if timestamp and timestamp < offset:
-                    t = rc.THROTTLED
-                    wait = timeout - (offset-timestamp).seconds
-                    t.content = 'Throttled, wait %d seconds.' % wait
-                    
-                    return t
-                    
-                count = cache.get(ident, 1)
-                cache.set(ident, count+1)
-                
-                if count >= max_requests:
-                    cache.set(ts_key, offset, timeout)
-                    cache.set(ident, 1)
-
-            return f(self, request, *args, **kwargs)
-        return wrap
-    return inner
+            if count >= max_requests:
+                cache.set(ts_key, offset, timeout)
+                cache.set(ident, 1)
+    
+        return f(self, request, *args, **kwargs)
+    return wrap
 
 def coerce_put_post(request):
     if request.method == "PUT":
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.