From 04c420f605b2bb7902f1afc3d62a45fbb0295bd0 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 18 Jan 2008 18:40:46 +0000 Subject: [PATCH] Merged revisions 60043-60052 via svnmerge from svn+ssh://pythondev@svn.python.org/python/trunk ........ r60043 | christian.heimes | 2008-01-18 10:51:43 +0100 (Fri, 18 Jan 2008) | 2 lines Build _ctypes after the other extensions. Its build process depends on the _weakref extension (and maybe other modules, too) ........ r60048 | christian.heimes | 2008-01-18 12:58:50 +0100 (Fri, 18 Jan 2008) | 2 lines Added win_add2path.py to Tools/scripts/ Added builddoc.bat to Doc/ ........ r60049 | vinay.sajip | 2008-01-18 16:54:14 +0100 (Fri, 18 Jan 2008) | 1 line Added section on passing contextual information to logging and documentation for the LoggerAdapter class. ........ r60050 | vinay.sajip | 2008-01-18 16:55:57 +0100 (Fri, 18 Jan 2008) | 1 line Added LoggerAdapter class, changed copyright dates, made check for extra parameter passed to logging methods explicitly against None rather than a truth value. ........ r60051 | georg.brandl | 2008-01-18 17:42:57 +0100 (Fri, 18 Jan 2008) | 2 lines Note that genexps are function scopes too and therefore won't see class attributes. ........ r60052 | christian.heimes | 2008-01-18 19:24:07 +0100 (Fri, 18 Jan 2008) | 1 line Added bytes and b'' as aliases for str and '' ........ --- Doc/builddoc.bat | 52 +++++++++++ Doc/library/logging.rst | 153 ++++++++++++++++++++++++++----- Doc/reference/executionmodel.rst | 8 +- Lib/logging/__init__.py | 100 +++++++++++++++++++- Tools/scripts/win_add2path.py | 57 ++++++++++++ 5 files changed, 341 insertions(+), 29 deletions(-) create mode 100644 Doc/builddoc.bat create mode 100644 Tools/scripts/win_add2path.py diff --git a/Doc/builddoc.bat b/Doc/builddoc.bat new file mode 100644 index 00000000000..a26851fb787 --- /dev/null +++ b/Doc/builddoc.bat @@ -0,0 +1,52 @@ +@echo off +setlocal + +set SVNROOT=http://svn.python.org/projects +if "%PYTHON%" EQU "" set PYTHON=python25 + +if "%1" EQU "" goto help +if "%1" EQU "html" goto build +if "%1" EQU "htmlhelp" goto build +if "%1" EQU "web" goto build +if "%1" EQU "webrun" goto webrun +if "%1" EQU "checkout" goto checkout +if "%1" EQU "update" goto update + +:help +echo HELP +echo. +echo builddoc checkout +echo builddoc update +echo builddoc html +echo builddoc htmlhelp +echo builddoc web +echo builddoc webrun +echo. +goto end + +:checkout +svn co %SVNROOT%/doctools/trunk/sphinx tools/sphinx +svn co %SVNROOT%/external/docutils-0.4/docutils tools/docutils +svn co %SVNROOT%/external/Pygments-0.9/pygments tools/pygments +goto end + +:update +svn update tools/sphinx +svn update tools/docutils +svn update tools/pygments +goto end + +:build +if not exist build mkdir build +if not exist build\%1 mkdir build\%1 +if not exist build\doctrees mkdir build\doctrees +cmd /C %PYTHON% tools\sphinx-build.py -b%1 -dbuild\doctrees . build\%1 +if "%1" EQU "htmlhelp" "%ProgramFiles%\HTML Help Workshop\hhc.exe" build\htmlhelp\pydoc.hhp +goto end + +:webrun +set PYTHONPATH=tools +%PYTHON% -m sphinx.web build\web +goto end + +:end diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index af8c86724b6..258bb0aaeea 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -1136,33 +1136,113 @@ level of granularity you want to use in logging an application, it could be hard to manage if the number of :class:`Logger` instances becomes effectively unbounded. -There are a number of other ways you can pass contextual information to be -output along with logging event information. +An easy way in which you can pass contextual information to be output along +with logging event information is to use the :class:`LoggerAdapter` class. +This class is designed to look like a :class:`Logger`, so that you can call +:meth:`debug`, :meth:`info`, :meth:`warning`, :meth:`error`, +:meth:`exception`, :meth:`critical` and :meth:`log`. These methods have the +same signatures as their counterparts in :class:`Logger`, so you can use the +two types of instances interchangeably. -* Use an adapter class which has access to the contextual information and - which defines methods :meth:`debug`, :meth:`info` etc. with the same - signatures as used by :class:`Logger`. You instantiate the adapter with a - name, which will be used to create an underlying :class:`Logger` with that - name. In each adpater method, the passed-in message is modified to include - whatever contextual information you want. +When you create an instance of :class:`LoggerAdapter`, you pass it a +:class:`Logger` instance and a dict-like object which contains your contextual +information. When you call one of the logging methods on an instance of +:class:`LoggerAdapter`, it delegates the call to the underlying instance of +:class:`Logger` passed to its constructor, and arranges to pass the contextual +information in the delegated call. Here's a snippet from the code of +:class:`LoggerAdapter`:: -* Use something other than a string to pass the message. Although normally - the first argument to a logger method such as :meth:`debug`, :meth:`info` - etc. is usually a string, it can in fact be any object. This object is the - argument to a :func:`str()` call which is made, in - :meth:`LogRecord.getMessage`, to obtain the actual message string. You can - use this behavior to pass an instance which may be initialized with a - logging message, which redefines :meth:__str__ to return a modified version - of that message with the contextual information added. + def debug(self, msg, *args, **kwargs): + """ + Delegate a debug call to the underlying logger, after adding + contextual information from this adapter instance. + """ + msg, kwargs = self.process(msg, kwargs) + self.logger.debug(msg, *args, **kwargs) -* Use a specialized :class:`Formatter` subclass to add additional information - to the formatted output. The subclass could, for instance, merge some thread - local contextual information (or contextual information obtained in some - other way) with the output generated by the base :class:`Formatter`. +The :meth:`process` method of :class:`LoggerAdapter` is where the contextual +information is added to the logging output. It's passed the message and +keyword arguments of the logging call, and it passes back (potentially) +modified versions of these to use in the call to the underlying logger. The +default implementation of this method leaves the message alone, but inserts +an "extra" key in the keyword argument whose value is the dict-like object +passed to the constructor. Of course, if you had passed an "extra" keyword +argument in the call to the adapter, it will be silently overwritten. + +The advantage of using "extra" is that the values in the dict-like object are +merged into the :class:`LogRecord` instance's __dict__, allowing you to use +customized strings with your :class:`Formatter` instances which know about +the keys of the dict-like object. If you need a different method, e.g. if you +want to prepend or append the contextual information to the message string, +you just need to subclass :class:`LoggerAdapter` and override :meth:`process` +to do what you need. Here's an example script which uses this class, which +also illustrates what dict-like behaviour is needed from an arbitrary +"dict-like" object for use in the constructor:: + +import logging + +class ConnInfo: + """ + An example class which shows how an arbitrary class can be used as + the 'extra' context information repository passed to a LoggerAdapter. + """ + + def __getitem__(self, name): + """ + To allow this instance to look like a dict. + """ + from random import choice + if name == "ip": + result = choice(["127.0.0.1", "192.168.0.1"]) + elif name == "user": + result = choice(["jim", "fred", "sheila"]) + else: + result = self.__dict__.get(name, "?") + return result + + def __iter__(self): + """ + To allow iteration over keys, which will be merged into + the LogRecord dict before formatting and output. + """ + keys = ["ip", "user"] + keys.extend(self.__dict__.keys()) + return keys.__iter__() + +if __name__ == "__main__": + from random import choice + levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL) + a1 = logging.LoggerAdapter(logging.getLogger("a.b.c"), + { "ip" : "123.231.231.123", "user" : "sheila" }) + logging.basicConfig(level=logging.DEBUG, + format="%(asctime)-15s %(name)-5s %(levelname)-8s IP: %(ip)-15s User: %(user)-8s %(message)s") + a1.debug("A debug message") + a1.info("An info message with %s", "some parameters") + a2 = logging.LoggerAdapter(logging.getLogger("d.e.f"), ConnInfo()) + for x in range(10): + lvl = choice(levels) + lvlname = logging.getLevelName(lvl) + a2.log(lvl, "A message at %s level with %d %s", lvlname, 2, "parameters") + +When this script is run, the output should look something like this:: + +2008-01-18 14:49:54,023 a.b.c DEBUG IP: 123.231.231.123 User: sheila A debug message +2008-01-18 14:49:54,023 a.b.c INFO IP: 123.231.231.123 User: sheila An info message with some parameters +2008-01-18 14:49:54,023 d.e.f CRITICAL IP: 192.168.0.1 User: jim A message at CRITICAL level with 2 parameters +2008-01-18 14:49:54,033 d.e.f INFO IP: 192.168.0.1 User: jim A message at INFO level with 2 parameters +2008-01-18 14:49:54,033 d.e.f WARNING IP: 192.168.0.1 User: sheila A message at WARNING level with 2 parameters +2008-01-18 14:49:54,033 d.e.f ERROR IP: 127.0.0.1 User: fred A message at ERROR level with 2 parameters +2008-01-18 14:49:54,033 d.e.f ERROR IP: 127.0.0.1 User: sheila A message at ERROR level with 2 parameters +2008-01-18 14:49:54,033 d.e.f WARNING IP: 192.168.0.1 User: sheila A message at WARNING level with 2 parameters +2008-01-18 14:49:54,033 d.e.f WARNING IP: 192.168.0.1 User: jim A message at WARNING level with 2 parameters +2008-01-18 14:49:54,033 d.e.f INFO IP: 192.168.0.1 User: fred A message at INFO level with 2 parameters +2008-01-18 14:49:54,033 d.e.f WARNING IP: 192.168.0.1 User: sheila A message at WARNING level with 2 parameters +2008-01-18 14:49:54,033 d.e.f WARNING IP: 127.0.0.1 User: jim A message at WARNING level with 2 parameters + +.. versionadded:: 2.6 + +The :class:`LoggerAdapter` class was not present in previous versions. -In each of these three approaches, thread locals can sometimes be a useful way -of passing contextual information without undue coupling between different -parts of your code. .. _network-logging: @@ -2047,6 +2127,33 @@ made, and any exception information to be logged. Returns the message for this :class:`LogRecord` instance after merging any user-supplied arguments with the message. +LoggerAdapter Objects +--------------------- + +.. versionadded:: 2.6 + +:class:`LoggerAdapter` instances are used to conveniently pass contextual +information into logging calls. For a usage example , see context-info_. + +.. class:: LoggerAdapter(logger, extra) + + Returns an instance of :class:`LoggerAdapter` initialized with an + underlying :class:`Logger` instance and a dict-like object. + +.. method:: LoggerAdapter.process(msg, kwargs) + + Modifies the message and/or keyword arguments passed to a logging call in + order to insert contextual information. This implementation takes the + object passed as *extra* to the constructor and adds it to *kwargs* using + key 'extra'. The return value is a (*msg*, *kwargs*) tuple which has the + (possibly modified) versions of the arguments passed in. + +In addition to the above, :class:`LoggerAdapter` supports all the logging +methods of :class:`Logger`, i.e. :meth:`debug`, :meth:`info`, :meth:`warning`, +:meth:`error`, :meth:`exception`, :meth:`critical` and :meth:`log`. These +methods have the same signatures as their counterparts in :class:`Logger`, so +you can use the two types of instances interchangeably. + Thread Safety ------------- diff --git a/Doc/reference/executionmodel.rst b/Doc/reference/executionmodel.rst index 1f85e49d4b3..43515d94ef1 100644 --- a/Doc/reference/executionmodel.rst +++ b/Doc/reference/executionmodel.rst @@ -50,7 +50,13 @@ variable is defined in a block, its scope includes that block. If the definition occurs in a function block, the scope extends to any blocks contained within the defining one, unless a contained block introduces a different binding for the name. The scope of names defined in a class block is limited to the -class block; it does not extend to the code blocks of methods. +class block; it does not extend to the code blocks of methods -- this includes +generator expressions since they are implemented using a function scope. This +means that the following will fail:: + + class A: + a = 42 + b = list(a + i for i in range(10)) .. index:: single: environment diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 04e05b9c818..68fd10c625b 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2001-2007 by Vinay Sajip. All Rights Reserved. +# Copyright 2001-2008 by Vinay Sajip. All Rights Reserved. # # Permission to use, copy, modify, and distribute this software and its # documentation for any purpose and without fee is hereby granted, @@ -18,7 +18,7 @@ Logging package for Python. Based on PEP 282 and comments thereto in comp.lang.python, and influenced by Apache's log4j system. -Copyright (C) 2001-2007 Vinay Sajip. All Rights Reserved. +Copyright (C) 2001-2008 Vinay Sajip. All Rights Reserved. To use, simply 'import logging' and log away! """ @@ -38,8 +38,8 @@ except ImportError: __author__ = "Vinay Sajip " __status__ = "production" -__version__ = "0.5.0.3" -__date__ = "26 September 2007" +__version__ = "0.5.0.4" +__date__ = "18 January 2008" #--------------------------------------------------------------------------- # Miscellaneous module data @@ -1076,7 +1076,7 @@ class Logger(Filterer): specialized LogRecords. """ rv = LogRecord(name, level, fn, lno, msg, args, exc_info, func) - if extra: + if extra is not None: for key in extra: if (key in ["message", "asctime"]) or (key in rv.__dict__): raise KeyError("Attempt to overwrite %r in LogRecord" % key) @@ -1189,6 +1189,96 @@ class RootLogger(Logger): _loggerClass = Logger +class LoggerAdapter: + """ + An adapter for loggers which makes it easier to specify contextual + information in logging output. + """ + + def __init__(self, logger, extra): + """ + Initialize the adapter with a logger and a dict-like object which + provides contextual information. This constructor signature allows + easy stacking of LoggerAdapters, if so desired. + + You can effectively pass keyword arguments as shown in the + following example: + + adapter = LoggerAdapter(someLogger, dict(p1=v1, p2="v2")) + """ + self.logger = logger + self.extra = extra + + def process(self, msg, kwargs): + """ + Process the logging message and keyword arguments passed in to + a logging call to insert contextual information. You can either + manipulate the message itself, the keyword args or both. Return + the message and kwargs modified (or not) to suit your needs. + + Normally, you'll only need to override this one method in a + LoggerAdapter subclass for your specific needs. + """ + kwargs["extra"] = self.extra + return msg, kwargs + + def debug(self, msg, *args, **kwargs): + """ + Delegate a debug call to the underlying logger, after adding + contextual information from this adapter instance. + """ + msg, kwargs = self.process(msg, kwargs) + self.logger.debug(msg, *args, **kwargs) + + def info(self, msg, *args, **kwargs): + """ + Delegate an info call to the underlying logger, after adding + contextual information from this adapter instance. + """ + msg, kwargs = self.process(msg, kwargs) + self.logger.info(msg, *args, **kwargs) + + def warning(self, msg, *args, **kwargs): + """ + Delegate a warning call to the underlying logger, after adding + contextual information from this adapter instance. + """ + msg, kwargs = self.process(msg, kwargs) + self.logger.warning(msg, *args, **kwargs) + + def error(self, msg, *args, **kwargs): + """ + Delegate an error call to the underlying logger, after adding + contextual information from this adapter instance. + """ + msg, kwargs = self.process(msg, kwargs) + self.logger.error(msg, *args, **kwargs) + + def exception(self, msg, *args, **kwargs): + """ + Delegate an exception call to the underlying logger, after adding + contextual information from this adapter instance. + """ + msg, kwargs = self.process(msg, kwargs) + kwargs["exc_info"] = 1 + self.logger.error(msg, *args, **kwargs) + + def critical(self, msg, *args, **kwargs): + """ + Delegate a critical call to the underlying logger, after adding + contextual information from this adapter instance. + """ + msg, kwargs = self.process(msg, kwargs) + self.logger.critical(msg, *args, **kwargs) + + def log(self, level, msg, *args, **kwargs): + """ + Delegate a log call to the underlying logger, after adding + contextual information from this adapter instance. + """ + msg, kwargs = self.process(msg, kwargs) + self.logger.log(level, msg, *args, **kwargs) + root = RootLogger(WARNING) Logger.root = root Logger.manager = Manager(Logger.root) diff --git a/Tools/scripts/win_add2path.py b/Tools/scripts/win_add2path.py new file mode 100644 index 00000000000..876bfb29240 --- /dev/null +++ b/Tools/scripts/win_add2path.py @@ -0,0 +1,57 @@ +"""Add Python to the search path on Windows + +This is a simple script to add Python to the Windows search path. It +modifies the current user (HKCU) tree of the registry. + +Copyright (c) 2008 by Christian Heimes +Licensed to PSF under a Contributor Agreement. +""" + +import sys +import site +import os +import _winreg + +HKCU = _winreg.HKEY_CURRENT_USER +ENV = "Environment" +PATH = "PATH" +DEFAULT = u"%PATH%" + +def modify(): + pythonpath = os.path.dirname(os.path.normpath(sys.executable)) + scripts = os.path.join(pythonpath, "Scripts") + appdata = os.environ["APPDATA"] + if hasattr(site, "USER_SITE"): + userpath = site.USER_SITE.replace(appdata, "%APPDATA%") + userscripts = os.path.join(userpath, "Scripts") + else: + userscripts = None + + with _winreg.CreateKey(HKCU, ENV) as key: + try: + envpath = _winreg.QueryValueEx(key, PATH)[0] + except WindowsError: + envpath = DEFAULT + + paths = [envpath] + for path in (pythonpath, scripts, userscripts): + if path and path not in envpath and os.path.isdir(path): + paths.append(path) + + envpath = os.pathsep.join(paths) + _winreg.SetValueEx(key, PATH, 0, _winreg.REG_EXPAND_SZ, envpath) + return paths, envpath + +def main(): + paths, envpath = modify() + if len(paths) > 1: + print "Path(s) added:" + print '\n'.join(paths[1:]) + else: + print "No path was added" + print "\nPATH is now:\n%s\n" % envpath + print "Expanded:" + print _winreg.ExpandEnvironmentStrings(envpath) + +if __name__ == '__main__': + main()