Steve Losh committed ba9ac38 Merge

Merge with m.

Comments (0)

Files changed (9)

 django-hoptoad requires:
-* Python_ 2.5+ (preferably 2.6+ as that's what I've tested it with)
-* PyYAML_ (`pip install pyyaml` or `easy_install pyyaml`)
+* Python_ 2.6
 * Django_ 1.0+
 * A Hoptoad_ account
 .. _Python:
-.. _PyYAML:
     HOPTOAD_API_KEY = 'Your Hoptoad API key.'
+There are more advanced options that can be used to customize your Hoptoad_ notification process; please go to `Configuration guide <>`_ to see more advanced configuration and usage options.


     HOPTOAD_IGNORE_AGENTS = ['Googlebot', 'Yahoo! Slurp', 'YahooSeeker']
+Using SSL to POST to Hoptoad
+If you desire to use SSL and your account plan supports it, then you can use the following setting to enable SSL POSTs:
+This will force all HTTP requests to use SSL. There's always a possibility, due to either an account downgrade, or, an expiration of a SSL certificate that Hoptoad might return an error code of `402` on a POST. There is built-in support automatically to try to re-POST the same error message without using SSL. To enable this feature, just add this option as well:
+This will force a fallback to a non-SSL HTTP post to Hoptoad.
+Asynchronous POSTs and Request Handlers
+On a highly trafficked website, there is a noticeable degree of a delay when POST'ing to hoptoad -- either due to error limitations, network instability, or other acts of God that can cause an HTTP request to slow down or fail. To fix this, django-hoptoad will spawn a daemon thread, by default, that will spawn a thread pool, with 4 threads, to queue up all errors for maximum throughput. However, this can be configured to your heart's content, including changing the notification handler completely. 
+To control the number of threads spawned per threadpool, you can set the following variable to your desired thread count per threadpool:
+In django-hoptoad, this variable defaults to 4.
+There is also built-in support for various other methods of communicating **synchronously** with Hoptoad, and that requires just one more setting:
+    HOPTOAD_HANDLER = "blocking"
+This variable is set to "threadpool" by default. 
+There are a few handlers to choose from, (i.e. possible `HOPTOAD_HANDLER` settings):
+#### "threadpool" 
+> This is the default setting. Will return a daemonized thread with a 4 worker-thread thread pool to handle all enqueued errors.
+#### "blocking" 
+> This will switch from the thread pool approach to a blocking HTTP POST where the entire Django process is halted until this blocking call returns.
+Over time, there will be more custom handlers with various options to control them.
+What if you do not want to use any of the default handlers, but instead, use your own proprietary system?
+There is support for drop-in replacements of handlers so that you might write your own custom handler. It's actually quite simple. All that's needed is that you implement a class, that implements an `enqueue` method which takes two parameters, `payload`, and `timeout`. You also have to import the api that's needed to report.
+For example, consider the following class below:
+    from hoptoad.api import htv2
+    class SomeAwesomeReporting(object):
+        def enqueue(self, payload, timeout):
+            """This enqueue method is your own implementation"""
+  , timeout)
+You would need to also to set two variables in ``:
+    HOPTOAD_HANDLER = "/path/to/the/custom/"
+    HOPTOAD_HANDLER_CLASS = "SomeAwesomeReport"
+As you can see, `HOPTOAD_HANDLER` is the file location to the implementation of the custom handler and `HOPTOAD_HANDLER_CLASS` is the name of the actual class that implements the enqueue method.
+Hoptoad Notification URL
+Currently, Hoptoad has their notification URLs pointed to ``, but this has been the second time that this was changed. It, therefore, can be expected that this will change again, so it is configurable like so:
+    HOPTOAD_NOTIFICATION_URL = "Hoptoad Notification URL here."
+This defaults to ``.
+Hoptoad Setting Groupings
+As you've probably noticed, these hoptoad settings are getting to be extremely abundant, so in order to give you some organization support for your ``, we've included support for grouping them in a dictionary. You can group them using `HOPTOAD_SETTINGS` as a dictionary and django-hoptoad will have support for this:
+            'HOPTOAD_API_KEY' : 'a3eer..'
+            'HOPTOAD_HANDLER' : 'threadpool',
+            'HOPTOAD_THREAD_COUNT' : 2,
+            'HOPTOAD_USE_SSL' : True,
+            # etc, you get the point
+     }
 If you're having trouble you might want to take a look at the [Troubleshooting Guide][troubleshooting].
-[troubleshooting]: /troubleshooting/
+[troubleshooting]: /troubleshooting/


 from django.conf import settings
+from itertools import ifilter
 __version__ = 0.3
     if not hoptoad_settings:
         # do some backward compatibility work to combine all hoptoad
         # settings in a dictionary
-        for attr in filter(lambda a: a.startswith('HOPTOAD'), dir(settings)):
+        # for every attribute that starts with hoptoad
+        for attr in ifilter(lambda x: x.startswith('HOPTOAD'), dir(settings)):
             hoptoad_settings[attr] = getattr(settings, attr)
         if not hoptoad_settings:


 import sys
 import traceback
 import urllib2
-import yaml
 from xml.dom.minidom import getDOMImplementation
 from django.views.debug import get_safe_settings
 def _class_name(class_):
     return class_.__class__.__name__
-def _handle_errors(request, response, exc):
+def _handle_errors(request, response):
     if response:
         code = "Http%s" % response
         msg = "%(code)s: %(response)s at %(uri)s" % {
         return (code, msg)
-    excc, inst = sys.exc_info()[:2]
-    if exc:
-        excc = exc
-    return _class_name(excc), _parse_message(excc)
+    exc, inst = sys.exc_info()[:2]
+    return _class_name(inst), _parse_message(inst)
-def generate_payload(request, response=None, exc=None):
+def generate_payload(request_tuple):
     """Generate an XML payload for a Hoptoad notification.
-    request -- A Django HTTPRequest.
+    request_tuple -- A tuple containing a Django HTTPRequest and a possible
+                     response code.
+    request, response = request_tuple
     hoptoad_settings = get_hoptoad_settings()
-    p_error_class, p_message = _handle_errors(request, response, exc)
+    p_error_class, p_message = _handle_errors(request, response)
     # api v2 from:
     xdoc = getDOMImplementation().createDocument(None, "notice", None)
     r = urllib2.Request(notification_url, payload, headers)
         if timeout:
             # timeout is 2.6 addition!
     except urllib2.URLError:
-        # getcode is 2.6 addition!
-        status = response.getcode()
+        try:
+            # getcode is 2.6 addition!!
+            status = response.getcode()
+        except AttributeError:
+            # default to just code
+            status = response.code
         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 get_hoptoad_settings().get('HOPTOAD_NO_SSL_FALLBACK', False):
+                # 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?)


 various different protocols.
 import logging
+import os
+import imp
+import pprint
 from hoptoad import get_hoptoad_settings
 from hoptoad.handlers.threaded import ThreadedNotifier
+from hoptoad.handlers.blocking import BlockingNotifier
 logger = logging.getLogger(__name__)
     if handler.lower() == 'threadpool':
         threads = hoptoad_settings.get("HOPTOAD_THREAD_COUNT", 4)
         return ThreadedNotifier(threads , *args, **kwargs)
+    elif handler.lower() == 'blocking':
+        return BlockingNotifier(*args, **kwargs)
+    else:
+        _class_module = hoptoad_settings.get('HOPTOAD_HANDLER_CLASS', None)
+        if not _class_module:
+            # not defined, abort setting up hoptoad, skip it.
+            raise MiddlewareNotUsed
+        # module name that we should import from
+        _module_name = os.path.splitext(os.path.basename(handler))[0]
+        # load the module!
+        m = imp.load_module(_module_name,
+                            *imp.find_module(_module_name,
+                                             [os.path.dirname(handler)]))
+        # instantiate the class
+        return getattr(m, _class_module)(*args, **kwargs)


+import os
+import time
+import logging
+from hoptoad.api import htv2
+logger = logging.getLogger(__name__)
+class BlockingNotifier(object):
+    """A blocking Hoptoad notifier.  """
+    def __init__(self):
+        _threadname = "Hoptoad%s-%d" % (self.__class__.__name__, os.getpid())
+    def enqueue(self, payload, timeout):
+, timeout)


         self.handler = get_handler()
     def _ignore(self, request):
-        """Return True if the given request should be ignored, False otherwise."""
+        """Return True if the given request should be ignored,
+        False otherwise.
+        """
         ua = request.META.get('HTTP_USER_AGENT', '')
         return any( for i in self.ignore_agents)
         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.handler.enqueue(htv2.generate_payload((request, sc)),
         return response
         be used.
-        if not self._ignore(request):
-            self.handler.enqueue(htv2.generate_payload(request, exc=exc),
-                                 self.timeout)
+        if self._ignore(request):
+            return None
+        self.handler.enqueue(htv2.generate_payload((request, None)),
+                             self.timeout)
+        return None
     description='django-hoptoad is some simple Middleware for letting '
                 'Django-driven websites report their errors to Hoptoad.',
-    author='Steve Losh',
-    author_email='',
+    author='Steve Losh, Mahmoud Abdelkader',
+    author_email=',',
-    install_requires=['pyyaml'],
         'Development Status :: 4 - Beta',
         'Environment :: Web Environment',