Commits

Steve Losh committed fda4e89

Clean up after the merge.

* Fix the whitespace so I don't go crazy when editing in TextMate.
* Add some comments to the XML-building function about the API.
* Clean up a few things to make them more elegant and match the coding style.

Comments (0)

Files changed (9)

-Copyright (c) 2009 Steve Losh
+Copyright (c) 2009-2010 Steve Losh and contributors
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

hoptoad/__init__.py

 __version__ = 0.3
 VERSION = __version__
 NAME = "django-hoptoad"
-URL = "http://bitbucket.org/sjl/django-hoptoad"
+URL = "http://sjl.bitbucket.org/django-hoptoad/"
 
 
 def get_hoptoad_settings():
-    hoptoad_settings = getattr(settings, "HOPTOAD_SETTINGS", None)
-
+    hoptoad_settings = getattr(settings, 'HOPTOAD_SETTINGS', {})
+    
     if not hoptoad_settings:
         # do some backward compatibility work to combine all hoptoad
         # settings in a dictionary
-        hoptoad_settings = {}
-        # for every attribute that starts with hoptoad
-        for attr in itertools.ifilter(lambda x: x.startswith('HOPTOAD'),
-                                      dir(settings)):
+        for attr in filter(lambda a: a.startswith('HOPTOAD'), dir(settings)):
             hoptoad_settings[attr] = getattr(settings, attr)
-
+        
         if not hoptoad_settings:
             # there were no settings for hoptoad at all..
             # should probably log here
             raise MiddlewareNotUsed
-
+    
     return hoptoad_settings

hoptoad/api/htv1.py

     """Return an environment mapping for a notification from the given request."""
     env = dict( (str(k), str(v)) for (k, v) in get_safe_settings().items() )
     env.update( dict( (str(k), str(v)) for (k, v) in request.META.items() ) )
-
+    
     env['REQUEST_URI'] = request.build_absolute_uri()
-
+    
     return env
 
 def _parse_traceback(trace):
                     for filename, lineno, funcname, _
                     in traceback.extract_tb(trace) ]
     p_traceback.reverse()
-
+    
     return p_traceback
 
 def _parse_message(exc):
 
 def _generate_payload(request, exc=None, trace=None, message=None, error_class=None):
     """Generate a YAML payload for a Hoptoad notification.
-
+    
     Parameters:
     request -- A Django HTTPRequest.  This is required.
-
+    
     Keyword parameters:
     exc -- A Python Exception object.  If this is not given the
            mess parameter must be.
     p_environment = _parse_environment(request)
     p_request = _parse_request(request)
     p_session = _parse_session(request.session)
-
+    
     return yaml.dump({ 'notice': {
         'api_key':       settings.HOPTOAD_API_KEY,
         'error_class':   p_error_class,
 
 def _ride_the_toad(payload, timeout):
     """Send a notification (an HTTP POST request) to Hoptoad.
-
+    
     Parameters:
     payload -- the YAML payload for the request from _generate_payload()
     timeout -- the maximum timeout, in seconds, or None to use the default

hoptoad/api/htv2.py

 from hoptoad.api.htv1 import _parse_environment, _parse_request, _parse_session
 from hoptoad.api.htv1 import _parse_message
 
+
 def _class_name(class_):
     return class_.__class__.__name__
 
     if response:
         code = "Http%s" % response
         msg = "%(code)s: %(response)s at %(uri)s" % {
-                   'code' : code,
-                   'response' : {'Http403' : "Forbidden",
-                                 'Http404' : "Page not found"}[code],
-                   'uri' : request.build_absolute_uri()
-                }
+                   'code': code,
+                   'response': { 'Http403': "Forbidden",
+                                 'Http404': "Page not found" }[code],
+                   'uri': request.build_absolute_uri()
+        }
         return (code, msg)
-
+    
     excc, inst = sys.exc_info()[:2]
     if exc:
         excc = exc
 
 def generate_payload(request, response=None, exc=None):
     """Generate an XML payload for a Hoptoad notification.
-
+    
     Parameters:
     request -- A Django HTTPRequest.
-
+    
     """
     hoptoad_settings = get_hoptoad_settings()
     p_error_class, p_message = _handle_errors(request, response, exc)
-
+    
     # api v2 from: http://help.hoptoadapp.com/faqs/api-2/notifier-api-v2
     xdoc = getDOMImplementation().createDocument(None, "notice", None)
     notice = xdoc.firstChild
-
+    
     # /notice/@version -- should be 2.0
     notice.setAttribute('version', '2.0')
-
+    
     # /notice/api-key
     api_key = xdoc.createElement('api-key')
     api_key_data = xdoc.createTextNode(hoptoad_settings['HOPTOAD_API_KEY'])
     api_key.appendChild(api_key_data)
     notice.appendChild(api_key)
-
+    
     # /notice/notifier/name
     # /notice/notifier/version
     # /notice/notifier/url
         key.appendChild(value)
         notifier.appendChild(key)
     notice.appendChild(notifier)
-
+    
     # /notice/error/class
     # /notice/error/message
     error = xdoc.createElement('error')
         value = xdoc.createTextNode(value)
         key.appendChild(value)
         error.appendChild(key)
-
+    
     # /notice/error/backtrace/error/line
     backtrace = xdoc.createElement('backtrace')
     # i do this here because I'm afraid of circular reference..
         backtrace.appendChild(line)
     error.appendChild(backtrace)
     notice.appendChild(error)
-
+    
     # /notice/request
     xrequest = xdoc.createElement('request')
-
+    
     # /notice/request/url -- request.build_absolute_uri()
     xurl = xdoc.createElement('url')
     xurl_data = xdoc.createTextNode(request.build_absolute_uri())
     xurl.appendChild(xurl_data)
     xrequest.appendChild(xurl)
-
+    
     # /notice/request/component -- not sure..
     comp = xdoc.createElement('component')
     #comp_data = xdoc.createTextNode('')
     xrequest.appendChild(comp)
-
+    
     # /notice/request/action -- action which error occured
     # ... no fucking clue..
-    action = xdoc.createElement('action') # maybe GET/POST??
-    action_data = u"%s %s" % (request.method, request.META['PATH_INFO'])
-    action_data = xdoc.createTextNode(action_data)
-    action.appendChild(action_data)
-    xrequest.appendChild(action)
-
+    
+    # sjl: "actions" are the Rails equivalent of Django's views
+    #      Is there a way to figure out which view a request object went to
+    #      (if any)?  Anyway, it's not GET/POST so I'm commenting it for now.
+    
+    #action = xdoc.createElement('action') # maybe GET/POST??
+    #action_data = u"%s %s" % (request.method, request.META['PATH_INFO'])
+    #action_data = xdoc.createTextNode(action_data)
+    #action.appendChild(action_data)
+    #xrequest.appendChild(action)
+    
     # /notice/request/params/var -- check request.GET/request.POST
     params = xdoc.createElement('params')
     for key, value in _parse_request(request).iteritems():
         var.appendChild(value)
         params.appendChild(var)
     xrequest.appendChild(params)
-
+    
     # /notice/request/session/var -- check if sessions is enabled..
     sessions = xdoc.createElement('session')
     for key, value in _parse_session(request.session).iteritems():
         var.appendChild(value)
         sessions.appendChild(var)
     xrequest.appendChild(params)
-
+    
     # /notice/request/cgi-data/var -- all meta data
     cgidata = xdoc.createElement('cgi-data')
     for key, value in _parse_environment(request).iteritems():
         cgidata.appendChild(var)
     xrequest.appendChild(cgidata)
     notice.appendChild(xrequest)
-
+    
+    # /notice/server-environment
     serverenv = xdoc.createElement('server-environment')
+    
     # /notice/server-environment/project-root -- default to sys.path[0] 
     projectroot = xdoc.createElement('project-root')
     projectroot.appendChild(xdoc.createTextNode(sys.path[0]))
     serverenv.appendChild(projectroot)
+    
     # /notice/server-environment/environment-name -- environment name? wtf..
-    envname = xdoc.createElement('environment-name')
+    #envname = xdoc.createElement('environment-name')
     # no idea...
+    
+    # sjl: This is supposed to be set to something like "test", "staging",
+    #      or "production" to help you group the errors in the web interface.
+    #      I'm still thinking about the best way to support this.
+    
     # envname.appendChild(xdoc.createTextNode())
-    serverenv.appendChild(envname)
+    #serverenv.appendChild(envname)
     notice.appendChild(serverenv)
-
+    
     return xdoc.toxml('utf-8')
 
 def _ride_the_toad(payload, timeout, use_ssl):
     """Send a notification (an HTTP POST request) to Hoptoad.
-
+    
     Parameters:
-    payload -- the YAML payload for the request from _generate_payload()
+    payload -- the XML payload for the request from _generate_payload()
     timeout -- the maximum timeout, in seconds, or None to use the default
-
+    
     """
     headers = { 'Content-Type': 'text/xml' }
-
-    # url calculation
+    
     url_template = '%s://hoptoadapp.com/notifier_api/v2/notices'
     notification_url = url_template % ("https" if use_ssl else "http")
+    
     # allow the settings to override all urls
     notification_url = get_hoptoad_settings().get('HOPTOAD_NOTIFICATION_URL',
                                                    notification_url)
-
+    
     r = urllib2.Request(notification_url, payload, headers)
     try:
         if timeout:
-            # timeout is 2.6 addition!!
+            # timeout is 2.6 addition!
             response = urllib2.urlopen(r, timeout=timeout)
         else:
             response = urllib2.urlopen(r)
     except urllib2.URLError:
         pass
     else:
-        # getcode is 2.6 addition!!
+        # getcode is 2.6 addition!
         status = response.getcode()
-
-        if status == 403:
+        
+        if status == 403 and use_ssl:
             # if we can not use SSL, re-invoke w/o using SSL
             _ride_the_toad(payload, timeout, use_ssl=False)
+        if status == 403 and not use_ssl:
+            # we were not trying to use SSL but got a 403 anyway
+            # something else must be wrong (bad API key?)
+            pass
         if status == 422:
             # couldn't send to hoptoad..
             pass

hoptoad/handlers/__init__.py

 from hoptoad import get_hoptoad_settings
 from hoptoad.handlers.threaded import ThreadedNotifier
 
+
 logger = logging.getLogger(__name__)
 
-
 def get_handler(*args, **kwargs):
     """Returns an initialized handler object"""
     hoptoad_settings = get_hoptoad_settings()

hoptoad/handlers/threaded.py

+import logging
 import os
 import threading
 import time
-import logging
 
 from hoptoad.api import htv2
-
 from hoptoad.handlers.utils.threadpool import WorkRequest, ThreadPool
 from hoptoad.handlers.utils.threadpool import NoResultsPending
 
 
 logger = logging.getLogger(__name__)
 
-
 def _exception_handler(request, exc_info):
     """Rudimentary exception handler, simply log and moves on.
-
+    
     If there's no tuple, it means something went really wrong. Critically log
     and exit.
-
+    
     """
     if not isinstance(exc_info, tuple):
         logger.critical(str(request))
 
 class ThreadedNotifier(threading.Thread):
     """A daemon thread that spawns a threadpool of worker threads.
-
+    
     Waits for queue additions through the enqueue method.
-
     """
     def __init__(self, threadpool_threadcount, cb=None, exc_cb=None):
         _threadname = "Hoptoad%s-%d" % (self.__class__.__name__, os.getpid())
         self.callback = cb
         self.exc_callback = exc_cb or _exception_handler
         self.pool = ThreadPool(self.threads)
-        # start the thread pool
         self.start()
-
+    
     def enqueue(self, payload, timeout):
         request = WorkRequest(
             htv2.report,
             callback=self.callback,
             exc_callback=self.exc_callback
         )
-
+        
         # Put the request into the queue where the detached 'run' method will
         # poll its queue every 0.5 seconds and start working.
         self.pool.putRequest(request)
-
+    
     def run(self):
         """Actively poll the queue for requests and process them."""
         while True:

hoptoad/middleware.py

+import itertools
+import logging
 import re
-import logging
-import itertools
 
+from django.conf import settings
 from django.core.exceptions import MiddlewareNotUsed
-from django.conf import settings
 
 from hoptoad import get_hoptoad_settings
+from hoptoad.api import htv2
 from hoptoad.handlers import get_handler
-from hoptoad.api import htv2
 
 
 logger = logging.getLogger(__name__)
 
-
 class HoptoadNotifierMiddleware(object):
     def __init__(self):
         """Initialize the middleware."""
-
         hoptoad_settings = get_hoptoad_settings()
         self._init_middleware(hoptoad_settings)
-
+    
     def _init_middleware(self, hoptoad_settings):
-
         if 'HOPTOAD_API_KEY' not in hoptoad_settings:
-            # no api key, abort!
             raise MiddlewareNotUsed
-
+        
         if settings.DEBUG:
-            if not hoptoad_settings.get('HOPTOAD_NOTIFY_WHILE_DEBUG', None):
-                # do not use hoptoad if you're in debug mode..
+            if not hoptoad_settings.get('HOPTOAD_NOTIFY_WHILE_DEBUG', False):
                 raise MiddlewareNotUsed
-
+        
         self.timeout = hoptoad_settings.get('HOPTOAD_TIMEOUT', None)
         self.notify_404 = hoptoad_settings.get('HOPTOAD_NOTIFY_404', False)
         self.notify_403 = hoptoad_settings.get('HOPTOAD_NOTIFY_403', False)
-
-        ignorable_agents = hoptoad_settings.get('HOPTOAD_IGNORE_AGENTS', [])
-        self.ignore_agents = map(re.compile, ignorable_agents)
-
+        
+        ignore_agents = hoptoad_settings.get('HOPTOAD_IGNORE_AGENTS', [])
+        self.ignore_agents = map(re.compile, ignore_agents)
+        
         self.handler = get_handler()
-
+    
     def _ignore(self, request):
         """Return True if the given request should be ignored, False otherwise."""
         ua = request.META.get('HTTP_USER_AGENT', '')
         return any(i.search(ua) for i in self.ignore_agents)
-
+    
     def process_response(self, request, response):
         """Process a reponse object.
-
+        
         Hoptoad will be notified of a 404 error if the response is a 404
         and 404 tracking is enabled in the settings.
-
+        
         Hoptoad will be notified of a 403 error if the response is a 403
         and 403 tracking is enabled in the settings.
-
+        
         Regardless of whether Hoptoad is notified, the reponse object will
         be returned unchanged.
-
+        
         """
         if self._ignore(request):
             return response
-
+        
         sc = response.status_code
         if sc in [404, 403] and getattr(self, "notify_%d" % sc):
             self.handler.enqueue(htv2.generate_payload(request, response=sc),
                                  self.timeout)
-
+        
         return response
-
+    
     def process_exception(self, request, exc):
         """Process an exception.
-
+        
         Hoptoad will be notified of the exception and None will be
         returned so that Django's normal exception handling will then
         be used.
-
+        
         """
-        if self._ignore(request):
-            return None
-
-        self.handler.enqueue(htv2.generate_payload(request, exc=exc),
-                             self.timeout)
-        return None
-
+        if not self._ignore(request):
+            self.handler.enqueue(htv2.generate_payload(request, exc=exc),
+                                 self.timeout)
+    
 import urllib2
 from django.test import TestCase
-from django.conf import settings
+from hoptoad import get_hoptoad_settings
 
 class BasicTests(TestCase):
     """Basic tests like setup and connectivity."""
     
     def test_api_key_present(self):
         """Test to make sure an API key is present."""
-        self.assertTrue('HOPTOAD_API_KEY' in dir(settings),
+        hoptoad_settings = get_hoptoad_settings()
+        self.assertTrue('HOPTOAD_API_KEY' in hoptoad_settings,
             msg='The HOPTOAD_API_KEY setting is not present.')
-        self.assertTrue(settings.HOPTOAD_API_KEY,
+        self.assertTrue(hoptoad_settings['HOPTOAD_API_KEY'],
             msg='The HOPTOAD_API_KEY setting is blank.')
     
     def test_hoptoad_connectivity(self):
 import os
 from setuptools import setup, find_packages
 
+README_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'README.rst')
+
 setup(
     name='django-hoptoad',
     version='0.3',
-    description='django-hoptoad is some simple Middleware for letting Django-driven websites report their errors to Hoptoad.',
-    long_description=open(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'README.rst')).read(),
+    description='django-hoptoad is some simple Middleware for letting '
+                'Django-driven websites report their errors to Hoptoad.',
+    long_description=open(README_PATH).read(),
     author='Steve Losh',
     author_email='steve@stevelosh.com',
-    url='http://stevelosh.com/projects/django-hoptoad/',
+    url='http://sjl.bitbucket.org/django-hoptoad/',
     packages=find_packages(),
     install_requires=['pyyaml'],
     classifiers=[