Commits

Lynn Rees  committed 9254372 Merge

- update

  • Participants
  • Parent commits c621b2d, 154fcbb

Comments (0)

Files changed (2)

File wsgilog/ez_setup.py

+#!python
+"""Bootstrap setuptools installation
+
+If you want to use setuptools in your package's setup.py, just include this
+file in the same directory with it, and add this to the top of your setup.py::
+
+    from ez_setup import use_setuptools
+    use_setuptools()
+
+If you want to require a specific version of setuptools, set a download
+mirror, or use an alternate download directory, you can do so by supplying
+the appropriate options to ``use_setuptools()``.
+
+This file can also be run as a script to install or upgrade setuptools.
+"""
+import sys
+DEFAULT_VERSION = "0.6c7"
+DEFAULT_URL     = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
+
+md5_data = {
+    'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
+    'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
+    'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
+    'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
+    'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
+    'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
+    'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
+    'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
+    'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
+    'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
+    'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
+    'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
+    'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
+    'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
+    'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
+    'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
+    'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
+    'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
+    'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
+    'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
+    'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
+    'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
+    'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
+    'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
+    'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
+    'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
+    'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
+}
+
+import sys, os
+
+def _validate_md5(egg_name, data):
+    if egg_name in md5_data:
+        from md5 import md5
+        digest = md5(data).hexdigest()
+        if digest != md5_data[egg_name]:
+            print >>sys.stderr, (
+                "md5 validation of %s failed!  (Possible download problem?)"
+                % egg_name
+            )
+            sys.exit(2)
+    return data
+
+
+def use_setuptools(
+    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+    download_delay=15
+):
+    """Automatically find/download setuptools and make it available on sys.path
+
+    `version` should be a valid setuptools version number that is available
+    as an egg for download under the `download_base` URL (which should end with
+    a '/').  `to_dir` is the directory where setuptools will be downloaded, if
+    it is not already available.  If `download_delay` is specified, it should
+    be the number of seconds that will be paused before initiating a download,
+    should one be required.  If an older version of setuptools is installed,
+    this routine will print a message to ``sys.stderr`` and raise SystemExit in
+    an attempt to abort the calling script.
+    """
+    try:
+        import setuptools
+        if setuptools.__version__ == '0.0.1':
+            print >>sys.stderr, (
+            "You have an obsolete version of setuptools installed.  Please\n"
+            "remove it from your system entirely before rerunning this script."
+            )
+            sys.exit(2)
+    except ImportError:
+        egg = download_setuptools(version, download_base, to_dir, download_delay)
+        sys.path.insert(0, egg)
+        import setuptools; setuptools.bootstrap_install_from = egg
+
+    import pkg_resources
+    try:
+        pkg_resources.require("setuptools>="+version)
+
+    except pkg_resources.VersionConflict, e:
+        # XXX could we install in a subprocess here?
+        print >>sys.stderr, (
+            "The required version of setuptools (>=%s) is not available, and\n"
+            "can't be installed while this script is running. Please install\n"
+            " a more recent version first.\n\n(Currently using %r)"
+        ) % (version, e.args[0])
+        sys.exit(2)
+
+def download_setuptools(
+    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+    delay = 15
+):
+    """Download setuptools from a specified location and return its filename
+
+    `version` should be a valid setuptools version number that is available
+    as an egg for download under the `download_base` URL (which should end
+    with a '/'). `to_dir` is the directory where the egg will be downloaded.
+    `delay` is the number of seconds to pause before an actual download attempt.
+    """
+    import urllib2, shutil
+    egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
+    url = download_base + egg_name
+    saveto = os.path.join(to_dir, egg_name)
+    src = dst = None
+    if not os.path.exists(saveto):  # Avoid repeated downloads
+        try:
+            from distutils import log
+            if delay:
+                log.warn("""
+---------------------------------------------------------------------------
+This script requires setuptools version %s to run (even to display
+help).  I will attempt to download it for you (from
+%s), but
+you may need to enable firewall access for this script first.
+I will start the download in %d seconds.
+
+(Note: if this machine does not have network access, please obtain the file
+
+   %s
+
+and place it in this directory before rerunning this script.)
+---------------------------------------------------------------------------""",
+                    version, download_base, delay, url
+                ); from time import sleep; sleep(delay)
+            log.warn("Downloading %s", url)
+            src = urllib2.urlopen(url)
+            # Read/write all in one block, so we don't create a corrupt file
+            # if the download is interrupted.
+            data = _validate_md5(egg_name, src.read())
+            dst = open(saveto,"wb"); dst.write(data)
+        finally:
+            if src: src.close()
+            if dst: dst.close()
+    return os.path.realpath(saveto)
+
+def main(argv, version=DEFAULT_VERSION):
+    """Install or upgrade setuptools and EasyInstall"""
+
+    try:
+        import setuptools
+    except ImportError:
+        egg = None
+        try:
+            egg = download_setuptools(version, delay=0)
+            sys.path.insert(0,egg)
+            from setuptools.command.easy_install import main
+            return main(list(argv)+[egg])   # we're done here
+        finally:
+            if egg and os.path.exists(egg):
+                os.unlink(egg)
+    else:
+        if setuptools.__version__ == '0.0.1':
+            # tell the user to uninstall obsolete version
+            use_setuptools(version)
+
+    req = "setuptools>="+version
+    import pkg_resources
+    try:
+        pkg_resources.require(req)
+    except pkg_resources.VersionConflict:
+        try:
+            from setuptools.command.easy_install import main
+        except ImportError:
+            from easy_install import main
+        main(list(argv)+[download_setuptools(delay=0)])
+        sys.exit(0) # try to force an exit
+    else:
+        if argv:
+            from setuptools.command.easy_install import main
+            main(argv)
+        else:
+            print "Setuptools version",version,"or greater has been installed."
+            print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
+
+
+
+def update_md5(filenames):
+    """Update our built-in md5 registry"""
+
+    import re
+    from md5 import md5
+
+    for name in filenames:
+        base = os.path.basename(name)
+        f = open(name,'rb')
+        md5_data[base] = md5(f.read()).hexdigest()
+        f.close()
+
+    data = ["    %r: %r,\n" % it for it in md5_data.items()]
+    data.sort()
+    repl = "".join(data)
+
+    import inspect
+    srcfile = inspect.getsourcefile(sys.modules[__name__])
+    f = open(srcfile, 'rb'); src = f.read(); f.close()
+
+    match = re.search("\nmd5_data = {\n([^}]+)}", src)
+    if not match:
+        print >>sys.stderr, "Internal error!"
+        sys.exit(2)
+
+    src = src[:match.start(1)] + repl + src[match.end(1):]
+    f = open(srcfile,'w')
+    f.write(src)
+    f.close()
+
+
+if __name__=='__main__':
+    if len(sys.argv)>2 and sys.argv[1]=='--md5update':
+        update_md5(sys.argv[2:])
+    else:
+        main(sys.argv[1:])
+
+
+
+
+

File wsgilog/wsgilog/__init__.py

+# Copyright (c) 2007 L. C. Rees.  All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1.  Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3.  Neither the name of the Portable Site Information Project nor the names
+# of its contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+'''WSGI logging and event reporting middleware.'''
+
+import sys
+import logging
+from cgitb import html
+from logging.handlers import HTTPHandler, SysLogHandler 
+from logging.handlers import TimedRotatingFileHandler, SMTPHandler
+
+__all__ = ['WsgiLog', 'log']
+
+# File rotation constants
+BACKUPS = 1
+INTERVAL = 'h'
+# Default logger name (should be changed)
+LOGNAME = 'wsgilog.log'
+# Default 'environ' entries
+CATCHID = 'wsgilog.catch'
+LOGGERID = 'wsgilog.logger'
+# Current proposed 'environ' key signalling no middleware exception handling
+THROWERR = 'x-wsgiorg.throw_errors'
+# HTTP error messages
+HTTPMSG = '500 Internal error'
+ERRORMSG = 'Server got itself in trouble'
+# Default log formats
+DATEFORMAT = '%a, %d %b %Y %H:%M:%S'
+LOGFORMAT = '%(name)s: %(asctime)s %(levelname)-4s %(message)s'
+
+def _errapp(environ, start_response):
+    '''Default error handling WSGI application.'''
+    start_response(HTTPMSG, [('Content-type', 'text/plain')],
+        exc_info=sys.exc_info())
+    return [ERRORMSG]    
+
+def log(**kw):
+    '''Decorator for logging middleware.'''
+    def decorator(application):
+        return WsgiLog(application, **kw)
+    return decorator
+
+
+class LogIO(object):
+
+    '''File-like object for sending stdout output to a logger.'''    
+
+    def __init__(self, logger, level=logging.DEBUG):
+        # Set logger level
+        if level == logging.DEBUG:
+            self.logger = logger.debug
+        elif level == logging.CRITICAL:
+            self.logger = logger.critical
+        elif level == logging.ERROR:
+            self.logger = logger.warning
+        elif level == logging.WARNING:
+            self.logger = logger.warning
+        elif level == logging.INFO:
+            self.logger = logger.info        
+
+    def write(self, info):
+        '''Writes non-whitespace strings to logger.'''
+        if info.lstrip().rstrip() != '': self.logger(info)
+
+
+class WsgiLog(object):
+
+    '''Class for WSGI logging and event recording middleware.'''    
+
+    def __init__(self, application, **kw):
+        self.application = application
+        # Error handling WSGI app
+        self._errapp = kw.get('errapp', _errapp)
+        # Flag controlling logging
+        self.log = kw.get('log', True)
+        # Log if set
+        if self.log:
+            # Log error message 
+            self.message = kw.get('logmessage', ERRORMSG)
+            # Individual logger for WSGI app with custom name 'logname'
+            self.logger = logging.getLogger(kw.get('logname', LOGNAME))
+            # Set logger level
+            self.logger.setLevel(kw.get('loglevel', logging.DEBUG))
+            # Log formatter
+            format = logging.Formatter(
+                # Log entry format
+                kw.get('logformat', LOGFORMAT),
+                # Date format
+                kw.get('datefmt', DATEFORMAT))
+            # Coroutine for setting individual log handlers
+            def setlog(logger):
+                logger.setFormatter(format)
+                self.logger.addHandler(logger)
+            # Log to STDOUT
+            if 'tostream' in kw:
+                setlog(logging.StreamHandler())
+            # Log to a rotating file that with periodic backup deletions
+            if 'tofile' in kw:
+                setlog(TimedRotatingFileHandler(
+                    # Log file path
+                    kw.get('file', LOGNAME),
+                    # Interval to backup log file
+                    kw.get('interval', INTERVAL),
+                    # Number of backups to keep
+                    kw.get('backups', BACKUPS)))
+            # Send log entries to an email address
+            if 'toemail' in kw:
+                setlog(SMTPHandler(
+                    # Mail server
+                    kw.get('mailserver'),
+                    # From email address
+                    kw.get('frommail'),
+                    # To email address
+                    kw.get('toemail'),
+                    # Email subject
+                    kw.get('mailsubject')))
+            # Send log entries to a web server
+            if 'tohttp' in kw:
+                setlog(HTTPHandler(
+                    # Web server host
+                    kw.get('httphost'),
+                    # Web URL
+                    kw.get('httpurl'),
+                    # HTTP method 
+                    kw.get('httpmethod', 'GET')))
+            # Log to syslog
+            if 'tosyslog' in kw:
+                setlog(SysLogHandler(
+                    # syslog host
+                    kw.get('syshost', ('localhost', 514)),
+                    # syslog user
+                    kw.get('facility', 'LOG_USER')))
+            assert self.logger.handlers, 'At least one logging handler must be configured'   
+            # Redirect STDOUT to the logger
+            if 'toprint' in kw:
+                sys.stdout = LogIO(self.logger,
+                    # Sets log level STDOUT is displayed under
+                    kw.get('prnlevel', logging.DEBUG))
+        # Flag for sending HTML-formatted exception tracebacks to the browser
+        self.tohtml = kw.get('tohtml', False)
+        # Write HTML-formatted exception tracebacks to a file if provided
+        self.htmlfile = kw.get('htmlfile')
+                
+    def __call__(self, environ, start_response):
+        # Make logger available to other WSGI apps/middlware
+        if self.log: environ[LOGGERID] = self.logger
+        # Make catch method available to other WSGI apps/middleware
+        environ[CATCHID] = self.catch
+        # Let exceptions "bubble up" to WSGI server/gateway
+        if THROWERR in environ:
+            return self.application(environ, start_response)
+        # Try application
+        try:
+            return self.application(environ, start_response)
+        # Log and/or report any errors
+        except:
+            return self.catch(environ, start_response)
+
+    def catch(self, environ, start_response):
+        '''Exception catcher.'''
+        # Log exception
+        if self.log: self.logger.exception(self.message)
+        # Write HTML-formatted exception tracebacks to a file
+        if self.htmlfile is not None:
+            open(self.htmlfile, 'wb').write(html(sys.exc_info()))
+        # Send HTML-formatted exception tracebacks to the browser
+        if self.tohtml:
+            start_response(HTTPMSG, [('Content-type', 'text/html')])
+            return [html(sys.exc_info())]
+        # Return error handler
+        return self._errapp(environ, start_response)