Author: brosner
Date: Sun Jan  4 00:35:29 2009
New Revision: 129

Added:
    branches/pluggable-backends/AUTHORS
       - copied unchanged from r128, /trunk/AUTHORS
    branches/pluggable-backends/LICENSE
       - copied unchanged from r128, /trunk/LICENSE
    branches/pluggable-backends/MANIFEST.in
       - copied unchanged from r128, /trunk/MANIFEST.in
    branches/pluggable-backends/README
       - copied unchanged from r128, /trunk/README
    branches/pluggable-backends/docs/
       - copied from r128, /trunk/docs/
    branches/pluggable-backends/docs/index.txt
       - copied unchanged from r128, /trunk/docs/index.txt
    branches/pluggable-backends/docs/usage.txt
       - copied unchanged from r128, /trunk/docs/usage.txt
    branches/pluggable-backends/notification/templates/notification/full.txt
       - copied unchanged from r128,  
/trunk/notification/templates/notification/full.txt
     
branches/pluggable-backends/notification/templates/notification/notice.html
       - copied unchanged from r128,  
/trunk/notification/templates/notification/notice.html
    branches/pluggable-backends/setup.py
       - copied unchanged from r128, /trunk/setup.py
Removed:
     
branches/pluggable-backends/notification/management/commands/upgrade_notices.py
    branches/pluggable-backends/notification/templates/notification/plain.txt
     
branches/pluggable-backends/notification/templates/notification/teaser.html
Modified:
    branches/pluggable-backends/   (props changed)
    branches/pluggable-backends/notification/__init__.py
    branches/pluggable-backends/notification/engine.py
    branches/pluggable-backends/notification/lockfile.py
    branches/pluggable-backends/notification/models.py

Log:
pluggable-backends: Merged from trunk up to r128.

Modified: branches/pluggable-backends/notification/__init__.py
==============================================================================
--- branches/pluggable-backends/notification/__init__.py        (original)
+++ branches/pluggable-backends/notification/__init__.py        Sun Jan  4  
00:35:29 2009
@@ -0,0 +1,2 @@
+VERSION = (0, 1, 'pre')
+__version__ = '.'.join(map(str, VERSION))
\ No newline at end of file

Modified: branches/pluggable-backends/notification/engine.py
==============================================================================
--- branches/pluggable-backends/notification/engine.py  (original)
+++ branches/pluggable-backends/notification/engine.py  Sun Jan  4 00:35:29  
2009
@@ -1,6 +1,8 @@

+import sys
  import time
  import logging
+import traceback

  try:
      import cPickle as pickle
@@ -8,7 +10,9 @@
      import pickle

  from django.conf import settings
+from django.core.mail import mail_admins
  from django.contrib.auth.models import User
+from django.contrib.sites.models import Site

  from lockfile import FileLock, AlreadyLocked, LockTimeout

@@ -38,16 +42,26 @@

      try:
          for queued_batch in NoticeQueueBatch.objects.all():
-            notices = pickle.loads(str(queued_batch.pickled_data))
+            notices =  
pickle.loads(str(queued_batch.pickled_data).decode("base64"))
              for user, label, extra_context, on_site in notices:
                  user = User.objects.get(pk=user)
                  logging.info("emitting notice to %s" % user)
                  # call this once per user to be atomic and allow for  
logging to
                  # accurately show how long each takes.
-                notification.send([user], label, extra_context, on_site)
+                notification.send_now([user], label, extra_context,  
on_site)
                  sent += 1
              queued_batch.delete()
              batches += 1
+    except:
+        # get the exception
+        exc_class, e, t = sys.exc_info()
+        # email people
+        current_site = Site.objects.get_current()
+        subject = "[%s emit_notices] %r" % (current_site.name, e)
+        message = "%s" %  
("\n".join(traceback.format_exception(*sys.exc_info())),)
+        mail_admins(subject, message, fail_silently=True)
+        # log it as critical
+        logging.critical("an exception occurred: %r" % e)
      finally:
          logging.debug("releasing lock...")
          lock.release()

Modified: branches/pluggable-backends/notification/lockfile.py
==============================================================================
--- branches/pluggable-backends/notification/lockfile.py        (original)
+++ branches/pluggable-backends/notification/lockfile.py        Sun Jan  4  
00:35:29 2009
@@ -7,13 +7,13 @@

  Usage:

->>> lock = FileLock(_testfile())
+>>> lock = FileLock('somefile')
  >>> try:
  ...     lock.acquire()
  ... except AlreadyLocked:
-...     print _testfile(), 'is locked already.'
+...     print 'somefile', 'is locked already.'
  ... except LockFailed:
-...     print _testfile(), 'can\\'t be locked.'
+...     print 'somefile', 'can\\'t be locked.'
  ... else:
  ...     print 'got lock'
  got lock
@@ -21,7 +21,7 @@
  True
  >>> lock.release()

->>> lock = FileLock(_testfile())
+>>> lock = FileLock('somefile')
  >>> print lock.is_locked()
  False
  >>> with lock:
@@ -46,21 +46,28 @@
          UnlockError - base class for all unlocking exceptions
              AlreadyUnlocked - File was not locked.
              NotMyLock - File was locked but not by the current  
thread/process
-
-To do:
-    * Write more test cases
-      - verify that all lines of code are executed
-    * Describe on-disk file structures in the documentation.
  """

-from __future__ import division, with_statement
+from __future__ import division

+import sys
  import socket
  import os
+import thread
  import threading
  import time
  import errno
-import thread
+
+# Work with PEP8 and non-PEP8 versions of threading module.
+try:
+    threading.current_thread
+except AttributeError:
+    threading.current_thread = threading.currentThread
+    threading.Thread.get_name = threading.Thread.getName
+
+__all__ = ['Error', 'LockError', 'LockTimeout', 'AlreadyLocked',
+           'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock',
+           'LinkFileLock', 'MkdirFileLock', 'SQLiteFileLock']

  class Error(Exception):
      """
@@ -149,14 +156,15 @@
      """Base class for platform-specific lock classes."""
      def __init__(self, path, threaded=True):
          """
-        >>> lock = LockBase(_testfile())
+        >>> lock = LockBase('somefile')
+        >>> lock = LockBase('somefile', threaded=False)
          """
          self.path = path
          self.lock_file = os.path.abspath(path) + ".lock"
          self.hostname = socket.gethostname()
          self.pid = os.getpid()
          if threaded:
-            tname = "%x-" % thread.get_ident()
+            tname = "%s-" % threading.current_thread().get_name()
          else:
              tname = ""
          dirname = os.path.dirname(self.lock_file)
@@ -178,187 +186,45 @@

          * If timeout <= 0, raise AlreadyLocked immediately if the file is
            already locked.
-
-        >>> # As simple as it gets.
-        >>> lock = FileLock(_testfile())
-        >>> lock.acquire()
-        >>> lock.release()
-
-        >>> # No timeout test
-        >>> e1, e2 = threading.Event(), threading.Event()
-        >>> t = _in_thread(_lock_wait_unlock, e1, e2)
-        >>> e1.wait()         # wait for thread t to acquire lock
-        >>> lock2 = FileLock(_testfile())
-        >>> lock2.is_locked()
-        True
-        >>> lock2.i_am_locking()
-        False
-        >>> try:
-        ...   lock2.acquire(timeout=-1)
-        ... except AlreadyLocked:
-        ...   pass
-        ... except Exception, e:
-        ...   print 'unexpected exception', repr(e)
-        ... else:
-        ...   print 'thread', threading.currentThread().getName(),
-        ...   print 'erroneously locked an already locked file.'
-        ...   lock2.release()
-        ...
-        >>> e2.set()          # tell thread t to release lock
-        >>> t.join()
-
-        >>> # Timeout test
-        >>> e1, e2 = threading.Event(), threading.Event()
-        >>> t = _in_thread(_lock_wait_unlock, e1, e2)
-        >>> e1.wait() # wait for thread t to acquire filelock
-        >>> lock2 = FileLock(_testfile())
-        >>> lock2.is_locked()
-        True
-        >>> try:
-        ...   lock2.acquire(timeout=0.1)
-        ... except LockTimeout:
-        ...   pass
-        ... except Exception, e:
-        ...   print 'unexpected exception', repr(e)
-        ... else:
-        ...   lock2.release()
-        ...   print 'thread', threading.currentThread().getName(),
-        ...   print 'erroneously locked an already locked file.'
-        ...
-        >>> e2.set()
-        >>> t.join()
          """
-        pass
+        raise NotImplemented("implement in subclass")

      def release(self):
          """
          Release the lock.

          If the file is not locked, raise NotLocked.
-        >>> lock = FileLock(_testfile())
-        >>> lock.acquire()
-        >>> lock.release()
-        >>> lock.is_locked()
-        False
-        >>> lock.i_am_locking()
-        False
-        >>> try:
-        ...   lock.release()
-        ... except NotLocked:
-        ...   pass
-        ... except NotMyLock:
-        ...   print 'unexpected exception', NotMyLock
-        ... except Exception, e:
-        ...   print 'unexpected exception', repr(e)
-        ... else:
-        ...   print 'erroneously unlocked file'
-
-        >>> e1, e2 = threading.Event(), threading.Event()
-        >>> t = _in_thread(_lock_wait_unlock, e1, e2)
-        >>> e1.wait()
-        >>> lock2 = FileLock(_testfile())
-        >>> lock2.is_locked()
-        True
-        >>> lock2.i_am_locking()
-        False
-        >>> try:
-        ...   lock2.release()
-        ... except NotMyLock:
-        ...   pass
-        ... except Exception, e:
-        ...   print 'unexpected exception', repr(e)
-        ... else:
-        ...   print 'erroneously unlocked a file locked by another thread.'
-        ...
-        >>> e2.set()
-        >>> t.join()
          """
-        pass
+        raise NotImplemented("implement in subclass")

      def is_locked(self):
          """
          Tell whether or not the file is locked.
-        >>> lock = FileLock(_testfile())
-        >>> lock.acquire()
-        >>> lock.is_locked()
-        True
-        >>> lock.release()
-        >>> lock.is_locked()
-        False
          """
-        pass
+        raise NotImplemented("implement in subclass")

      def i_am_locking(self):
-        """Return True if this object is locking the file.
-
-        >>> lock1 = FileLock(_testfile(), threaded=False)
-        >>> lock1.acquire()
-        >>> lock2 = FileLock(_testfile())
-        >>> lock1.i_am_locking()
-        True
-        >>> lock2.i_am_locking()
-        False
-       >>> try:
-       ...   lock2.acquire(timeout=2)
-       ... except LockTimeout:
-        ...   lock2.break_lock()
-        ...   lock2.is_locked()
-        ...   lock1.is_locked()
-        ...   lock2.acquire()
-        ... else:
-        ...   print 'expected LockTimeout...'
-        ...
-        False
-        False
-        >>> lock1.i_am_locking()
-        False
-        >>> lock2.i_am_locking()
-        True
-        >>> lock2.release()
          """
-        pass
+        Return True if this object is locking the file.
+        """
+        raise NotImplemented("implement in subclass")

      def break_lock(self):
-        """Remove a lock.  Useful if a locking thread failed to unlock.
-
-        >>> lock = FileLock(_testfile())
-        >>> lock.acquire()
-        >>> lock2 = FileLock(_testfile())
-        >>> lock2.is_locked()
-        True
-        >>> lock2.break_lock()
-        >>> lock2.is_locked()
-        False
-        >>> try:
-        ...   lock.release()
-        ... except NotLocked:
-        ...   pass
-        ... except Exception, e:
-        ...   print 'unexpected exception', repr(e)
-        ... else:
-        ...   print 'break lock failed'
          """
-        pass
+        Remove a lock.  Useful if a locking thread failed to unlock.
+        """
+        raise NotImplemented("implement in subclass")

      def __enter__(self):
-        """Context manager support.
-
-        >>> lock = FileLock(_testfile())
-        >>> with lock:
-        ...   lock.is_locked()
-        ...
-        True
-        >>> lock.is_locked()
-        False
+        """
+        Context manager support.
          """
          self.acquire()
          return self

      def __exit__(self, *_exc):
-        """Context manager support.
-
-        >>> 'tested in __enter__'
-        'tested in __enter__'
+        """
+        Context manager support.
          """
          self.release()

@@ -366,23 +232,6 @@
      """Lock access to a file using atomic property of link(2)."""

      def acquire(self, timeout=None):
-        """
-        >>> d = _testfile()
-        >>> os.mkdir(d)
-        >>> os.chmod(d, 0444)
-        >>> try:
-        ...   lock = LinkFileLock(os.path.join(d, 'test'))
-        ...   try:
-        ...     lock.acquire()
-        ...   except LockFailed:
-        ...     pass
-        ...   else:
-        ...     lock.release()
-        ...     print 'erroneously locked', os.path.join(d, 'test')
-        ... finally:
-        ...   os.chmod(d, 0664)
-        ...   os.rmdir(d)
-        """
          try:
              open(self.unique_name, "wb").close()
          except IOError:
@@ -440,9 +289,10 @@
      """Lock file by creating a directory."""
      def __init__(self, path, threaded=True):
          """
-        >>> lock = MkdirFileLock(_testfile())
+        >>> lock = MkdirFileLock('somefile')
+        >>> lock = MkdirFileLock('somefile', threaded=False)
          """
-        LockBase.__init__(self, path)
+        LockBase.__init__(self, path, threaded)
          if threaded:
              tname = "%x-" % thread.get_ident()
          else:
@@ -467,7 +317,8 @@
          while True:
              try:
                  os.mkdir(self.lock_file)
-            except OSError, err:
+            except OSError:
+                err = sys.exc_info()[1]
                  if err.errno == errno.EEXIST:
                      # Already locked.
                      if os.path.exists(self.unique_name):
@@ -603,8 +454,7 @@
          if not self.is_locked():
              raise NotLocked
          if not self.i_am_locking():
-            raise NotMyLock, ("locker:", self._who_is_locking(),
-                              "me:", self.unique_name)
+            raise NotMyLock((self._who_is_locking(), self.unique_name))
          cursor = self.connection.cursor()
          cursor.execute("delete from locks"
                         "  where unique_name = ?",
@@ -645,84 +495,3 @@
      FileLock = LinkFileLock
  else:
      FileLock = MkdirFileLock
-
-def _in_thread(func, *args, **kwargs):
-    """Execute func(*args, **kwargs) after dt seconds.
-
-    Helper for docttests.
-    """
-    def _f():
-        func(*args, **kwargs)
-    t = threading.Thread(target=_f, name='/*/*')
-    t.start()
-    return t
-
-def _testfile():
-    """Return platform-appropriate lock file name.
-
-    Helper for doctests.
-    """
-    import tempfile
-    return os.path.join(tempfile.gettempdir(), 'trash-%s' % os.getpid())
-
-def _lock_wait_unlock(event1, event2):
-    """Lock from another thread.
-
-    Helper for doctests.
-    """
-    lock = FileLock(_testfile())
-    with lock:
-        event1.set()  # we're in,
-        event2.wait() # wait for boss's permission to leave
-
-def _test():
-    global FileLock
-
-    import doctest
-    import sys
-
-    def test_object(c):
-        nfailed = ntests = 0
-        for (obj, recurse) in ((c, True),
-                               (LockBase, True),
-                               (sys.modules["__main__"], False)):
-            tests = doctest.DocTestFinder(recurse=recurse).find(obj)
-            runner = doctest.DocTestRunner(verbose="-v" in sys.argv)
-            tests.sort(key = lambda test: test.name)
-            for test in tests:
-                f, t = runner.run(test)
-                nfailed += f
-                ntests += t
-        print FileLock.__name__, "tests:", ntests, "failed:", nfailed
-        return nfailed, ntests
-
-    nfailed = ntests = 0
-
-    if hasattr(os, "link"):
-        FileLock = LinkFileLock
-        f, t = test_object(FileLock)
-        nfailed += f
-        ntests += t
-
-    if hasattr(os, "mkdir"):
-        FileLock = MkdirFileLock
-        f, t = test_object(FileLock)
-        nfailed += f
-        ntests += t
-
-    try:
-        import sqlite3
-    except ImportError:
-        print "SQLite3 is unavailable - not testing SQLiteFileLock."
-    else:
-        print "Testing SQLiteFileLock with sqlite", sqlite3.sqlite_version,
-        print "& pysqlite", sqlite3.version
-        FileLock = SQLiteFileLock
-        f, t = test_object(FileLock)
-        nfailed += f
-        ntests += t
-
-    print "total tests:", ntests, "total failed:", nfailed
-
-if __name__ == "__main__":
-    _test()

Modified: branches/pluggable-backends/notification/models.py
==============================================================================
--- branches/pluggable-backends/notification/models.py  (original)
+++ branches/pluggable-backends/notification/models.py  Sun Jan  4 00:35:29  
2009
@@ -27,6 +27,15 @@
  from notification import backends
  from notification.message import encode_message

+# favour django-mailer but fall back to django.core.mail
+try:
+    mailer = models.get_app("mailer")
+    from mailer import send_mail
+except ImproperlyConfigured:
+    from django.core.mail import send_mail
+
+QUEUE_ALL = getattr(settings, "NOTIFICATION_QUEUE_ALL", False)
+
  class LanguageStoreNotAvailable(Exception):
      pass

@@ -165,7 +174,7 @@
      """
      pickled_data = models.TextField()

-def create_notice_type(label, display, description, default=2):
+def create_notice_type(label, display, description, default=2,  
verbosity=1):
      """
      Creates a new NoticeType.

@@ -185,10 +194,12 @@
              updated = True
          if updated:
              notice_type.save()
-            print "Updated %s NoticeType" % label
+            if verbosity > 1:
+                print "Updated %s NoticeType" % label
      except NoticeType.DoesNotExist:
          NoticeType(label=label, display=display, description=description,  
default=default).save()
-        print "Created %s NoticeType" % label
+        if verbosity > 1:
+            print "Created %s NoticeType" % label

  def get_notification_language(user):
      """
@@ -217,13 +228,12 @@
          # conditionally turn off autoescaping for .txt extensions in format
          if format.endswith(".txt"):
              context.autoescape = False
-        name = format.split(".")[0]
-        format_templates[name] = render_to_string((
+        format_templates[format] = render_to_string((
              'notification/%s/%s' % (label, format),
              'notification/%s' % format), context_instance=context)
      return format_templates

-def send(users, label, extra_context={}, on_site=True):
+def send_now(users, label, extra_context=None, on_site=True):
      """
      Creates a new notice.

@@ -239,6 +249,9 @@

      FIXME: this function needs some serious reworking.
      """
+    if extra_context is None:
+        extra_context = {}
+
      notice_type = NoticeType.objects.get(label=label)

      notice_type = NoticeType.objects.get(label=label)
@@ -254,8 +267,8 @@

      formats = (
          'short.txt',
-        'plain.txt',
-        'teaser.html',
+        'full.txt',
+        'notice.html',
          'full.html',
      ) # TODO make formats configurable

@@ -288,14 +301,14 @@
          # Strip newlines from subject
          # TODO: this should move to the email backend
          subject  
= ''.join(render_to_string('notification/email_subject.txt', {
-            'message': messages['short'],
+            'message': messages['short.txt'],
          }, context).splitlines())

          body = render_to_string('notification/email_body.txt', {
-            'message': messages['plain'],
+            'message': messages['full.txt'],
          }, context)

-        notice = Notice.objects.create(user=user,  
message=messages['teaser'],
+        notice = Notice.objects.create(user=user,  
message=messages['notice.html'],
              notice_type=notice_type, on_site=on_site)
          for key, backend in NOTIFICATION_BACKENDS:
              recipients = backend_recipients.setdefault(key, [])
@@ -307,7 +320,34 @@
      # reset environment to original language
      activate(current_language)

-def queue(users, label, extra_context={}, on_site=True):
+def send(*args, **kwargs):
+    """
+    A basic interface around both queue and send_now. This honors a global
+    flag NOTIFICATION_QUEUE_ALL that helps determine whether all calls  
should
+    be queued or not. A per call ``queue`` or ``now`` keyword argument can  
be
+    used to always override the default global behavior.
+    """
+    queue_flag = kwargs.pop("queue", False)
+    now_flag = kwargs.pop("now", False)
+    assert not (queue_flag and now_flag), "'queue' and 'now' cannot both  
be True."
+    if queue_flag:
+        return queue(*args, **kwargs)
+    elif now_flag:
+        return send_now(*args, **kwargs)
+    else:
+        if QUEUE_ALL:
+            return queue(*args, **kwargs)
+        else:
+            return send_now(*args, **kwargs)
+
+def queue(users, label, extra_context=None, on_site=True):
+    """
+    Queue the notification in NoticeQueueBatch. This allows for large  
amounts
+    of user notifications to be deferred to a seperate process running  
outside
+    the webserver.
+    """
+    if extra_context is None:
+        extra_context = {}
      if isinstance(users, QuerySet):
          users = [row["pk"] for row in users.values("pk")]
      else:
@@ -315,7 +355,7 @@
      notices = []
      for user in users:
          notices.append((user, label, extra_context, on_site))
-    NoticeQueueBatch(pickled_data=pickle.dumps(notices)).save()
+     
NoticeQueueBatch(pickled_data=pickle.dumps(notices).encode("base64")).save()

  class ObservedItemManager(models.Manager):


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"pinax-updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/pinax-updates?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to