Commits

Benjamin Peterson committed f547ebf

Merged revisions 62090-62091,62096,62100,62102,62110-62114 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk

........
r62090 | brett.cannon | 2008-04-01 07:37:43 -0500 (Tue, 01 Apr 2008) | 3 lines

Generalize test.test_support.test_stdout() with a base context manager so that
it is easy to capture stderr if desired.
........
r62091 | brett.cannon | 2008-04-01 07:46:02 -0500 (Tue, 01 Apr 2008) | 3 lines

Add ``if __name__ == '__main__'`` to some test files where it didn't take a lot
of effort to do so.
........
r62096 | amaury.forgeotdarc | 2008-04-01 17:52:48 -0500 (Tue, 01 Apr 2008) | 4 lines

Newly enabled test appears to leak:
it registers the same codec on each iteration.
Do it only once at load time.
........
r62100 | amaury.forgeotdarc | 2008-04-01 19:55:04 -0500 (Tue, 01 Apr 2008) | 4 lines

A DocTestSuite cannot run multiple times: it clears its globals dictionary after the first run.

Rebuild the DocTestSuite on each iteration.
........
r62102 | jeffrey.yasskin | 2008-04-01 23:07:44 -0500 (Tue, 01 Apr 2008) | 3 lines

Try to make test_signal less flaky. I still see some flakiness in
test_itimer_prof.
........
r62110 | vinay.sajip | 2008-04-02 16:09:27 -0500 (Wed, 02 Apr 2008) | 1 line

Fix: #2315, #2316, #2317: TimedRotatingFileHandler - changed logic to better handle daylight savings time, deletion of old log files, and fixed a bug in calculating rollover when no logging occurs for a longer interval than the rollover period.
........
r62111 | vinay.sajip | 2008-04-02 16:10:23 -0500 (Wed, 02 Apr 2008) | 1 line

Added updates with respect to recent changes to TimedRotatingFileHandler.
........
r62112 | vinay.sajip | 2008-04-02 16:17:25 -0500 (Wed, 02 Apr 2008) | 1 line

Added updates with respect to recent changes to TimedRotatingFileHandler.
........
r62113 | amaury.forgeotdarc | 2008-04-02 16:18:46 -0500 (Wed, 02 Apr 2008) | 2 lines

Remove debug prints; the buildbot now passes the tests
........
r62114 | benjamin.peterson | 2008-04-02 16:20:35 -0500 (Wed, 02 Apr 2008) | 2 lines

Suggested proposed changes to Python be considered on some mailing lists first
........

  • Participants
  • Parent commits 205d7fd

Comments (0)

Files changed (11)

File Doc/library/logging.rst

 
    The system will save old log files by appending extensions to the filename.
    The extensions are date-and-time based, using the strftime format
-   ``%Y-%m-%d_%H-%M-%S`` or a leading portion thereof, depending on the rollover
-   interval. If *backupCount* is nonzero, at most *backupCount* files will be
-   kept, and if more would be created when rollover occurs, the oldest one is
-   deleted.
+   ``%Y-%m-%d_%H-%M-%S`` or a leading portion thereof, depending on the
+   rollover interval. If *backupCount* is nonzero, at most *backupCount* files
+   will be kept, and if more would be created when rollover occurs, the oldest
+   one is deleted. The deletion logic uses the interval to determine which
+   files to delete, so changing the interval may leave old files lying around.
 
 
 .. method:: TimedRotatingFileHandler.doRollover()

File Lib/logging/handlers.py

 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!
 """
 
-import logging, socket, os, pickle, struct, time, glob
+import logging, socket, os, pickle, struct, time, re
 from stat import ST_DEV, ST_INO
 
 try:
         if self.when == 'S':
             self.interval = 1 # one second
             self.suffix = "%Y-%m-%d_%H-%M-%S"
+            self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}$"
         elif self.when == 'M':
             self.interval = 60 # one minute
             self.suffix = "%Y-%m-%d_%H-%M"
+            self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}$"
         elif self.when == 'H':
             self.interval = 60 * 60 # one hour
             self.suffix = "%Y-%m-%d_%H"
+            self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}$"
         elif self.when == 'D' or self.when == 'MIDNIGHT':
             self.interval = 60 * 60 * 24 # one day
             self.suffix = "%Y-%m-%d"
+            self.extMatch = r"^\d{4}-\d{2}-\d{2}$"
         elif self.when.startswith('W'):
             self.interval = 60 * 60 * 24 * 7 # one week
             if len(self.when) != 2:
                 raise ValueError("Invalid day specified for weekly rollover: %s" % self.when)
             self.dayOfWeek = int(self.when[1])
             self.suffix = "%Y-%m-%d"
+            self.extMatch = r"^\d{4}-\d{2}-\d{2}$"
         else:
             raise ValueError("Invalid rollover interval specified: %s" % self.when)
 
+        self.extMatch = re.compile(self.extMatch)
         self.interval = self.interval * interval # multiply by units requested
         self.rolloverAt = currentTime + self.interval
 
                         daysToWait = self.dayOfWeek - day
                     else:
                         daysToWait = 6 - day + self.dayOfWeek + 1
-                    self.rolloverAt = self.rolloverAt + (daysToWait * (60 * 60 * 24))
+                    newRolloverAt = self.rolloverAt + (daysToWait * (60 * 60 * 24))
+                    dstNow = t[-1]
+                    dstAtRollover = time.localtime(newRolloverAt)[-1]
+                    if dstNow != dstAtRollover:
+                        if not dstNow:  # DST kicks in before next rollover, so we need to deduct an hour
+                            newRolloverAt = newRolloverAt - 3600
+                        else:           # DST bows out before next rollover, so we need to add an hour
+                            newRolloverAt = newRolloverAt + 3600
+                    self.rolloverAt = newRolloverAt
 
         #print "Will rollover at %d, %d seconds from now" % (self.rolloverAt, self.rolloverAt - currentTime)
 
     def shouldRollover(self, record):
         """
-        Determine if rollover should occur
+        Determine if rollover should occur.
 
         record is not used, as we are just comparing times, but it is needed so
-        the method siguratures are the same
+        the method signatures are the same
         """
         t = int(time.time())
         if t >= self.rolloverAt:
         #print "No need to rollover: %d, %d" % (t, self.rolloverAt)
         return 0
 
+    def getFilesToDelete(self):
+        """
+        Determine the files to delete when rolling over.
+
+        More specific than the earlier method, which just used glob.glob().
+        """
+        dirName, baseName = os.path.split(self.baseFilename)
+        fileNames = os.listdir(dirName)
+        result = []
+        prefix = baseName + "."
+        plen = len(prefix)
+        for fileName in fileNames:
+            if fileName[:plen] == prefix:
+                suffix = fileName[plen:]
+                if self.extMatch.match(suffix):
+                    result.append(fileName)
+        result.sort()
+        if len(result) < self.backupCount:
+            result = []
+        else:
+            result = result[:len(result) - self.backupCount]
+        return result
+
     def doRollover(self):
         """
         do a rollover; in this case, a date/time stamp is appended to the filename
         os.rename(self.baseFilename, dfn)
         if self.backupCount > 0:
             # find the oldest log file and delete it
-            s = glob.glob(self.baseFilename + ".20*")
-            if len(s) > self.backupCount:
-                s.sort()
-                os.remove(s[0])
+            #s = glob.glob(self.baseFilename + ".20*")
+            #if len(s) > self.backupCount:
+            #    s.sort()
+            #    os.remove(s[0])
+            for s in self.getFilesToDelete():
+                os.remove(s)
         #print "%s -> %s" % (self.baseFilename, dfn)
         self.mode = 'w'
         self.stream = self._open()
-        self.rolloverAt = self.rolloverAt + self.interval
+        newRolloverAt = self.rolloverAt + self.interval
+        currentTime = int(time.time())
+        while newRolloverAt <= currentTime:
+            newRolloverAt = newRolloverAt + self.interval
+        #If DST changes and midnight or weekly rollover, adjust for this.
+        if self.when == 'MIDNIGHT' or self.when.startswith('W'):
+            dstNow = time.localtime(currentTime)[-1]
+            dstAtRollover = time.localtime(newRolloverAt)[-1]
+            if dstNow != dstAtRollover:
+                if not dstNow:  # DST kicks in before next rollover, so we need to deduct an hour
+                    newRolloverAt = newRolloverAt - 3600
+                else:           # DST bows out before next rollover, so we need to add an hour
+                    newRolloverAt = newRolloverAt + 3600
+        self.rolloverAt = newRolloverAt
 
 class WatchedFileHandler(logging.FileHandler):
     """

File Lib/test/test_code.py

     from test.test_support import run_doctest
     from test import test_code
     run_doctest(test_code, verbose)
+
+
+if __name__ == '__main__':
+    test_main()

File Lib/test/test_collections.py

         self.failIf(issubclass(str, MutableSequence))
 
 import doctest, collections
-NamedTupleDocs = doctest.DocTestSuite(module=collections)
 
 def test_main(verbose=None):
-    import collections as CollectionsModule
+    NamedTupleDocs = doctest.DocTestSuite(module=collections)
     test_classes = [TestNamedTuple, NamedTupleDocs, TestOneTrickPonyABCs, TestCollectionABCs]
     test_support.run_unittest(*test_classes)
-    test_support.run_doctest(CollectionsModule, verbose)
+    test_support.run_doctest(collections, verbose)
 
 
 if __name__ == "__main__":

File Lib/test/test_io.py

         self.buffer = bytearray()
         return output
 
+    codecEnabled = False
+
+    @classmethod
+    def lookupTestDecoder(cls, name):
+        if cls.codecEnabled and name == 'test_decoder':
+            return codecs.CodecInfo(
+                name='test_decoder', encode=None, decode=None,
+                incrementalencoder=None,
+                streamreader=None, streamwriter=None,
+                incrementaldecoder=cls)
+
+# Register the previous decoder for testing.
+# Disabled by default, tests will enable it.
+codecs.register(StatefulIncrementalDecoder.lookupTestDecoder)
+
+
 class StatefulIncrementalDecoderTest(unittest.TestCase):
     """
     Make sure the StatefulIncrementalDecoder actually works.
     def testSeekAndTell(self):
         """Test seek/tell using the StatefulIncrementalDecoder."""
 
-        def lookupTestDecoder(name):
-            if self.codecEnabled and name == 'test_decoder':
-                return codecs.CodecInfo(
-                    name='test_decoder', encode=None, decode=None,
-                    incrementalencoder=None,
-                    streamreader=None, streamwriter=None,
-                    incrementaldecoder=StatefulIncrementalDecoder)
-
         def testSeekAndTellWithData(data, min_pos=0):
             """Tell/seek to various points within a data stream and ensure
             that the decoded data returned by read() is consistent."""
                     self.assertEquals(f.read(), decoded[i:])
                     f.close()
 
-        # Register a special incremental decoder for testing.
-        codecs.register(lookupTestDecoder)
-        self.codecEnabled = 1
+        # Enable the test decoder.
+        StatefulIncrementalDecoder.codecEnabled = 1
 
         # Run the tests.
         try:
 
         # Ensure our test decoder won't interfere with subsequent tests.
         finally:
-            self.codecEnabled = 0
+            StatefulIncrementalDecoder.codecEnabled = 0
 
     def testEncodedWrites(self):
         data = "1234567890"

File Lib/test/test_lib2to3.py

 
 def test_main():
     run_unittest(suite())
+
+
+if __name__ == '__main__':
+    test_main()

File Lib/test/test_signal.py

     os._exit(0)
 
 
+def ignoring_eintr(__func, *args, **kwargs):
+    try:
+        return __func(*args, **kwargs)
+    except IOError as e:
+        if e.errno != signal.EINTR:
+            raise
+        return None
+
+
 class InterProcessSignalTests(unittest.TestCase):
     MAX_DURATION = 20   # Entire test should last at most 20 sec.
 
         if test_support.verbose:
             print("test runner's pid is", pid)
 
-        child = subprocess.Popen(['kill', '-HUP', str(pid)])
-        self.wait(child)
+        child = ignoring_eintr(subprocess.Popen, ['kill', '-HUP', str(pid)])
+        if child:
+            self.wait(child)
+            if not self.a_called:
+                time.sleep(1)  # Give the signal time to be delivered.
         self.assertTrue(self.a_called)
         self.assertFalse(self.b_called)
         self.a_called = False
             child = subprocess.Popen(['kill', '-USR1', str(pid)])
             # This wait should be interrupted by the signal's exception.
             self.wait(child)
+            time.sleep(1)  # Give the signal time to be delivered.
             self.fail('HandlerBCalled exception not thrown')
         except HandlerBCalled:
             self.assertTrue(self.b_called)
             if test_support.verbose:
                 print("HandlerBCalled exception caught")
 
-        child = subprocess.Popen(['kill', '-USR2', str(pid)])
-        self.wait(child)  # Nothing should happen.
+        child = ignoring_eintr(subprocess.Popen, ['kill', '-USR2', str(pid)])
+        if child:
+            self.wait(child)  # Nothing should happen.
 
         try:
             signal.alarm(1)
             # since alarm is going to raise a KeyboardException, which
             # will skip the call.
             signal.pause()
+            # But if another signal arrives before the alarm, pause
+            # may return early.
+            time.sleep(1)
         except KeyboardInterrupt:
             if test_support.verbose:
                 print("KeyboardInterrupt (the alarm() went off)")
         except:
-            self.fail('Some other exception woke us from pause: %s' %
+            self.fail("Some other exception woke us from pause: %s" %
                       traceback.format_exc())
         else:
-            self.fail('pause returned of its own accord')
+            self.fail("pause returned of its own accord, and the signal"
+                      " didn't arrive after another second.")
 
     def test_main(self):
         # This function spawns a child process to insulate the main

File Lib/test/test_socket.py

 from weakref import proxy
 import signal
 
-# Temporary hack to see why test_socket hangs on one buildbot
-if os.environ.get('COMPUTERNAME') == "GRAPE":
-    def verbose_write(arg):
-        print(arg, file=sys.__stdout__)
-else:
-    def verbose_write(arg):
-        pass
-
 PORT = 50007
 HOST = 'localhost'
 MSG = b'Michael Gilfix was here\n'
 class SocketTCPTest(unittest.TestCase):
 
     def setUp(self):
-        verbose_write(self)
         self.serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         self.serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-        verbose_write(str(self) + " socket created")
         global PORT
         PORT = test_support.bind_port(self.serv, HOST, PORT)
-        verbose_write(str(self) + " start listening")
         self.serv.listen(1)
-        verbose_write(str(self) + " started")
 
     def tearDown(self):
-        verbose_write(str(self) + " close")
         self.serv.close()
         self.serv = None
-        verbose_write(str(self) + " done")
 
 class SocketUDPTest(unittest.TestCase):
 

File Lib/test/test_support.py

 
 
 @contextlib.contextmanager
-def captured_stdout():
-    """Run the with statement body using a StringIO object as sys.stdout.
-    Example use::
+def captured_output(stream_name):
+    """Run the 'with' statement body using a StringIO object in place of a
+    specific attribute on the sys module.
+    Example use (with 'stream_name=stdout')::
 
        with captured_stdout() as s:
            print "hello"
        assert s.getvalue() == "hello"
     """
     import io
-    orig_stdout = sys.stdout
-    sys.stdout = io.StringIO()
-    yield sys.stdout
-    sys.stdout = orig_stdout
+    orig_stdout = getattr(sys, stream_name)
+    setattr(sys, stream_name, io.StringIO())
+    yield getattr(sys, stream_name)
+    setattr(sys, stream_name, orig_stdout)
+
+def captured_stdout():
+    return captured_output("stdout")
 
 
 #=======================================================================
   as a build_py replacement to automatically run 2to3 on modules
   that are going to be installed.
 
+- Issue #2315: logging.handlers: TimedRotatingFileHandler now accounts for
+  daylight savings time in calculating the next rollover.
+
+- Issue #2316: logging.handlers: TimedRotatingFileHandler  now calculates
+  rollovers correctly even when nothing is logged for a while.
+
+- Issue #2317: logging.handlers: TimedRotatingFileHandler now uses improved
+  logic for removing old files.
+
+- Issue #2495: tokenize.untokenize now inserts a space between two consecutive
+  string literals; previously, ["" ""] was rendered as [""""], which is
+  incorrect python code.
+
 - A new pickle protocol (protocol 3) is added with explicit support
   for bytes.  This is the default protocol.  It intentionally cannot
   be unpickled by Python 2.x.
 What's New in Python 3.0a3?
 ===========================
 
+- Issue #1681432:  Add triangular distribution to the random module
+
+- Issue #2136: urllib2's auth handler now allows single-quoted realms in the
+  WWW-Authenticate header.
+
+- Issue #2434: Enhanced platform.win32_ver() to also work on Python
+  installation which do not have the win32all package installed.
+
+- Added support to platform.uname() to also report the machine
+  and processor information on Windows XP and later. As a result,
+  platform.machine() and platform.processor() will report this
+  information as well.
+
+- The library implementing the 2to3 conversion, lib2to3, was added
+  to the standard distribution.
+
+- Issue #1747858: Fix chown to work with large uid's and gid's on 64-bit
+  platforms.
+
+- Issue #1202: zlib.crc32 and zlib.adler32 no longer return different values
+  on 32-bit vs. 64-bit python interpreters.  Both were correct, but they now
+  both return a signed integer object for consistency.
+
+- Issue #1158: add %f format (fractions of a second represented as
+  microseconds) to datetime objects.  Understood by both strptime and
+  strftime.
+
+- Issue #705836: struct.pack(">f", x) now raises OverflowError on all
+  platforms when x is too large to fit into an IEEE 754 float; previously
+  it only raised OverflowError on non IEEE 754 platforms.
+
+- Issue #1106316: pdb.post_mortem()'s parameter, "traceback", is now
+  optional: it defaults to the traceback of the exception that is currently
+  being handled (is mandatory to be in the middle of an exception, otherwise
+  it raises ValueError).
+
+- Issue #1193577: A .shutdown() method has been added to SocketServers
+  which terminates the .serve_forever() loop.
+
+- Bug #2220: handle rlcompleter attribute match failure more gracefully.
+
+- Issue #2225: py_compile, when executed as a script, now returns a non-
+  zero status code if not all files could be compiled successfully.
+
+- Bug #1725737: In distutil's sdist, exclude RCS, CVS etc. also in the
+  root directory, and also exclude .hg, .git, .bzr, and _darcs.
+
+- Issue #1872: The struct module typecode for _Bool has been changed
+  from 't' to '?'.
+
+- The bundled libffi copy is now in sync with the recently released
+  libffi3.0.4 version, apart from some small changes to
+  Modules/_ctypes/libffi/configure.ac.
+  On OS X, preconfigured libffi files are used.
+  On all linux systems the --with-system-ffi configure option defaults
+  to "yes".
+
+- Issue 1577: shutil.move() now calls os.rename() if the destination is a
+  directory instead of copying-then-remove-source.
+
+Tests
+-----
+
+- test_nis no longer fails when test.test_support.verbose is true and NIS is
+  not set up on the testing machine.
+
+- Output comparison tests are no longer supported.
+
+- Rewrite test_errno to use unittest and no longer be a no-op.
+
+- GHOP 234: Convert test_extcall to doctest.
+
+- GHOP 290: Convert test_dbm and test_dummy_threading to unittest.
+
+- GHOP 293: Convert test_strftime, test_getargs, and test_pep247 to unittest.
+
+- Issue #2055: Convert test_fcntl to unittest.
+
+- Issue 1960: Convert test_gdbm to unittest.
+
+- GHOP 294: Convert test_contains, test_crypt, and test_select to unittest.
+
+- GHOP 238: Convert test_tokenize to use doctest.
+
+- GHOP 237: Rewrite test_thread using unittest.
+
+- Patch #2232: os.tmpfile might fail on Windows if the user has no
+  permission to create files in the root directory.
+
+Build
+-----
+
+- A new script 2to3 is now installed, to run the 2.x to 3.x converter.
+
+- Python/memmove.c and Python/strerror.c have been removed; both functions are
+  in the C89 standard library.
+
+- Patch #2284: Add -x64 option to rt.bat.
+
+C API
+-----
+
+- Patch #2477: Added PyParser_ParseFileFlagsEx() and
+  PyParser_ParseStringFlagsFilenameEx()
+
+What's New in Python 2.6 alpha 1?
+=================================
+
+>>>>>>> .merge-right.r62114
 *Release date: 29-Feb-2008*
 
 Core and Builtins
 
 - Issue #2282: io.TextIOWrapper was not overriding seekable() from io.IOBase.
 
-- Issue #2115: Important speedup in setting __slot__ attributes.  Also 
-  prevent a possible crash: an Abstract Base Class would try to access a slot 
+- Issue #2067: file.__exit__() now calls subclasses' close() method.
+
+- Patch #1759: Backport of PEP 3129 class decorators.
+
+- Issue #1881: An internal parser limit has been increased. Also see
+  issue 215555 for a discussion.
+
+- Added the future_builtins module, which contains hex() and oct().
+  These are the PEP 3127 version of these functions, designed to be
+  compatible with the hex() and oct() builtins from Python 3.0.  They
+  differ slightly in their output formats from the existing, unchanged
+  Python 2.6 builtins.  The expected usage of the future_builtins
+  module is:
+    from future_builtins import hex, oct
+
+- Issue #1600: Modifed PyOS_ascii_formatd to use at most 2 digit
+  exponents for exponents with absolute value < 100.  Follows C99
+  standard.  This is a change on Windows, which would use 3 digits.
+  Also, added 'n' to the formats that PyOS_ascii_formatd understands,
+  so that any alterations it does to the resulting string will be
+  available in stringlib/formatter.h (for float.__format__).
+
+- Implemented PEP 3101, Advanced String Formatting.  This adds a new
+  builtin format(); a format() method for str and unicode; a
+  __format__() method to object, str, unicode, int, long, float, and
+  datetime; the class string.Formatter; and the C API
+  PyObject_Format().
+
+- Fixed several potential crashes, all caused by specially crafted __del__
+  methods exploiting objects in temporarily inconsistent state.
+
+>>>>>>> .merge-right.r62114
+- Issue #2115: Important speedup in setting __slot__ attributes.  Also
+  prevent a possible crash: an Abstract Base Class would try to access a slot
   on a registered virtual subclass.
 
 - Fixed repr() and str() of complex numbers with infinity or nan as real or
 
 - Issue #1969: split and rsplit in bytearray are inconsistent
 
+- Issue #1920: "while 0" statements were completely removed by the compiler,
+  even in the presence of an "else" clause, which is supposed to be run when
+  the condition is false. Now the compiler correctly emits bytecode for the
+  "else" suite.
+
 - map() and no longer accepts None for the first argument.
   Use zip() instead.
 
 - Removed these C APIs:
   PyNumber_Coerce(), PyNumber_CoerceEx(), PyMember_Get, PyMember_Set
 
+- ``PySet_Add()`` can now modify a newly created frozenset.  Similarly to
+  ``PyTuple_SetItem``, it can be used to populate a brand new frozenset; but
+  it does not steal a reference to the added item.
+
 - Removed these C slots/fields:
   nb_divide, nb_inplace_divide
 
+- Added ``PySet_Check()`` and ``PyFrozenSet_Check()`` to the set API.
+
 - Removed these macros:
   staticforward, statichere, PyArg_GetInt, PyArg_NoArgs, _PyObject_Del
 
 Tools/Demos
 -----------
 
+- Bug #1542693: remove semi-colon at end of PyImport_ImportModuleEx macro
+  so it can be used as an expression.
+
+
+Windows
+-------
+
+- Patch #1706: Drop support for Win9x, WinME and NT4. Python now requires
+  Windows 2000 or greater. The _WINVER and NTDDI_VERSION macros are set to
+  Win2k for x86/32bit builds and WinXP for AMD64 builds.
+
+- Conditionalize definition of _CRT_SECURE_NO_DEPRECATE
+  and _CRT_NONSTDC_NO_DEPRECATE.
+
+- Bug #1216: Restore support for Visual Studio 2002.
+
+
+Mac
+---
+
+- cfmfile now raises a DeprecationWarning.
+
+- buildtools now raises a DeprecationWarning.
+
+- Removed the macfs module.  It had been deprecated since Python 2.5.  This
+  lead to the deprecation of macostools.touched() as it relied solely on macfs
+  and was a no-op under OS X.
+
+----
+
 **(For information about older versions, consult the HISTORY file.)**
 Proposals for enhancement
 ------------------------------
 
-If you have a proposal to change Python, it's best to submit a Python
-Enhancement Proposal (PEP) first.  All current PEPs, as well as guidelines for
-submitting a new PEP, are listed at http://www.python.org/dev/peps/.
+If you have a proposal to change Python, you may want to send an email to the
+comp.lang.python or python-ideas mailing lists for inital feedback. A Python
+Enhancement Proposal (PEP) may be submitted if your idea gains ground. All
+current PEPs, as well as guidelines for submitting a new PEP, are listed at
+http://www.python.org/dev/peps/.
 
 Converting From Python 2.x to 3.0
 ---------------------------------