Index: Python/import.c =================================================================== --- Python/import.c (revision 54146) +++ Python/import.c (working copy) @@ -157,7 +157,7 @@ void _PyImportHooks_Init(void) { - PyObject *v, *path_hooks = NULL, *zimpimport; + PyObject *v, *path_hooks = NULL, *zimpimport, *oldlib; int err = 0; /* adding sys.path_hooks and sys.path_importer_cache, setting up @@ -186,13 +186,8 @@ if (path_hooks == NULL) goto error; err = PySys_SetObject("path_hooks", path_hooks); - if (err) { - error: - PyErr_Print(); - Py_FatalError("initializing sys.meta_path, sys.path_hooks, " - "path_importer_cache, or NullImporter failed" - ); - } + if (err) + goto error; zimpimport = PyImport_ImportModule("zipimport"); if (zimpimport == NULL) { @@ -221,7 +216,49 @@ "# installed zipimport hook\n"); } } + + oldlib = PyImport_ImportModule("oldlib"); + if (oldlib == NULL) { + PyErr_Clear(); /* No oldlib mapper module -- okay */ + if (Py_VerboseFlag) + PySys_WriteStderr("# can't import oldlib\n"); + } + else { + PyObject *mapper_class = PyObject_GetAttrString( + oldlib, "OldStdlibLoader"); + + Py_DECREF(oldlib); + if (mapper_class == NULL) { + PyErr_Clear(); /* No oldlib mapper object -- okay */ + if (Py_VerboseFlag) + PySys_WriteStderr( + "# can't import oldlib.OldStdlibLoader\n"); + } + else { + PyObject *mapper = PyObject_CallFunction( + mapper_class, NULL); + + Py_DECREF(mapper_class); + if (mapper == NULL) + goto error; + err = PySys_SetObject("stdlib_remapper", mapper); + Py_DECREF(mapper); + if (err) + goto error; + if (Py_VerboseFlag) + PySys_WriteStderr( + "# installed oldlib remapper\n"); + } + } + Py_DECREF(path_hooks); + return; + + error: + PyErr_Print(); + Py_FatalError("initializing sys.meta_path, sys.path_hooks, " + "path_importer_cache, or NullImporter failed" + ); } void Index: Include/cStringIO.h =================================================================== --- Include/cStringIO.h (revision 54146) +++ Include/cStringIO.h (working copy) @@ -5,9 +5,9 @@ #endif /* - This header provides access to cStringIO objects from C. - Functions are provided for calling cStringIO objects and - macros are provided for testing whether you have cStringIO + This header provides access to cstringio objects from C. + Functions are provided for calling cstringio objects and + macros are provided for testing whether you have cstringio objects. Before calling any of the functions or macros, you must initialize @@ -19,10 +19,10 @@ */ #define PycString_IMPORT \ - PycStringIO = (struct PycStringIO_CAPI*)PyCObject_Import("cStringIO", \ + PycStringIO = (struct PycStringIO_CAPI*)PyCObject_Import("cstringio", \ "cStringIO_CAPI") -/* Basic functions to manipulate cStringIO objects from C */ +/* Basic functions to manipulate cstringio objects from C */ static struct PycStringIO_CAPI { @@ -51,7 +51,7 @@ */ PyObject *(*NewInput)(PyObject *); - /* The Python types for cStringIO input and output objects. + /* The Python types for cstringio input and output objects. Note that you can do input on an output object. */ PyTypeObject *InputType, *OutputType; Index: setup.py =================================================================== --- setup.py (revision 54146) +++ setup.py (working copy) @@ -429,7 +429,7 @@ exts.append( Extension('parser', ['parsermodule.c']) ) # cStringIO and cPickle - exts.append( Extension('cStringIO', ['cStringIO.c']) ) + exts.append( Extension('cstringio', ['cStringIO.c']) ) exts.append( Extension('cPickle', ['cPickle.c']) ) # Memory-mapped files (also works on Win32). Index: Doc/lib/libsys.tex =================================================================== --- Doc/lib/libsys.tex (revision 54146) +++ Doc/lib/libsys.tex (working copy) @@ -402,6 +402,15 @@ \versionchanged[Unicode strings are no longer ignored]{2.3} \end{datadesc} +\begin{datadesc}{stdlib_remapper} + This is an object providing programmatic access to the ``standard library + remapper''. In Python 3.0, the standard library will be reorganized, and + this object manages the mappings from the old Python 2.x names to the Python + 3.0 names. Use this object if you want to manipulate these mappings, + especially to add third party new module names. See the \refmodule{oldlib} + module for details. +\end{datadesc} + \begin{datadesc}{platform} This string contains a platform identifier, e.g. \code{'sunos5'} or \code{'linux1'}. This can be used to append platform-specific Index: Doc/lib/liboldlib.tex =================================================================== --- Doc/lib/liboldlib.tex (revision 0) +++ Doc/lib/liboldlib.tex (revision 0) @@ -0,0 +1,73 @@ +\section{\module{oldlib} --- + Standard library module remapper.} + +\declaremodule{standard}{oldlib} +\modulesynopsis{Standard library module remapper.} + +\versionadded{2.6} + +This module provides \pep{XXXX} standard library module name remappings. Python +3.0 will reorganize the standard library, but it is possible (perhaps even +desirable) to start using the new module names as of Python 2.6. + +The specifics of the module renamings are not described here. Please see +\pep{3108} or the individual modules for those details. + +The system-wide remapper instance is available on \code{sys.stdlib_remapper}, +and its class should never be instantiated except by Python itself. When +instantiated, this object scans the data files inside the \module{oldlib} +package directory, looking for \file{.mv} files. These files contain the +mappings, one per line with two whitespace separated fields. The first field +is the full dotted-path name of the old module, and the second field is the +full dotted-path name of the new module. Blank lines and lines starting with +\character{\#} are ignored. + +Note that the package must be physically located under the new name; the old +name is an alias only. Here is a partial example of an \file{.mv} file from +the standard library: + +\begin{verbatim} +# Map the various string i/o libraries to their new names +StringIO stringio +cStringIO cstringio +\end{verbatim} + +Here are the methods provided by the \code{sys.stdlib_remapper} object: + +\begin{methoddesc}{read_mv_file}{filename} + Open and read \var{filename} for stdlib remappings. + + In the file named by \var{filename}, blank lines and lines that start with a + \character{\#} are ignored. All other lines contain two whitespace + separated fields. The first field is the full dotted-path old module name + and the second field is the full dotted-path new module name. All + remappings in the named file are registered. +\end{methoddesc} + +\begin{methoddesc}{read_directory_mv_files}{dirname\optional{, suffix}} + Parse the remappings in all \file{.mv} files inside \var{dirname}. + + This method lists the given directory, then opens and reads all \file{.mv} + files in that directory, registering all module remappings found. Optional + \var{suffix} can specific a remapping file suffix other than \file{.mv}. +\end{methoddesc} + +\begin{methoddesc}{set_mapping}{oldname, newname} + Register additional module renamings. + + \var{oldname} is the full dotted-path old name for the module. + \var{newname} is the full dotted-path new name for the module. If a mapping + for \var{oldname} already exists, it will be replaced. Use + \method{get_mapping()} to see if a mapping for oldname already exists. + + If \var{newname} is \code{None}, then the \var{oldname} remapping is removed + if it exists. No error is raised if the mapping does not exist. +\end{methoddesc} + +\begin{methoddesc}{get_mapping}{oldname\optional{, default}} + Return the new module name for \var{oldname}, if it exists. + + If there is no existing mapping for \var{oldname}, \var{default} is + returned (defaults to \code{None}). Both \var{oldname} and the returned + mapping value must be the full dotted-path module name. +\end{methoddesc} Index: Doc/lib/lib.tex =================================================================== --- Doc/lib/lib.tex (revision 54146) +++ Doc/lib/lib.tex (working copy) @@ -380,6 +380,7 @@ \input{libatexit} \input{libtraceback} \input{libfuture} % really __future__ +\input{liboldlib} \input{libgc} \input{libinspect} \input{libsite} Index: Lib/site.py =================================================================== --- Lib/site.py (revision 54146) +++ Lib/site.py (working copy) @@ -64,7 +64,10 @@ def makepath(*paths): - dir = os.path.abspath(os.path.join(*paths)) + joined_path = os.path.join(*paths) + if joined_path.startswith('<'): + return joined_path, joined_path + dir = os.path.abspath(joined_path) return dir, os.path.normcase(dir) def abs__file__(): @@ -97,12 +100,18 @@ # XXX This should not be part of site.py, since it is needed even when # using the -S option for Python. See http://www.python.org/sf/586680 +def findmodulesdir(): + for relpath in reversed(sys.path): + if not relpath.startswith('<'): + return relpath + def addbuilddir(): """Append ./build/lib. in case we're running in the build dir (especially for Guido :-)""" from distutils.util import get_platform s = "build/lib.%s-%.3s" % (get_platform(), sys.version) - s = os.path.join(os.path.dirname(sys.path[-1]), s) + relpath = findmodulesdir() + s = os.path.join(os.path.dirname(relpath), s) sys.path.append(s) def _init_pathinfo(): @@ -395,7 +404,7 @@ abs__file__() paths_in_sys = removeduppaths() if (os.name == "posix" and sys.path and - os.path.basename(sys.path[-1]) == "Modules"): + os.path.basename(findmodulesdir()) == "Modules"): addbuilddir() paths_in_sys = addsitepackages(paths_in_sys) if sys.platform == 'os2emx': Index: Lib/oldlib/stdlib.mv =================================================================== --- Lib/oldlib/stdlib.mv (revision 0) +++ Lib/oldlib/stdlib.mv (revision 0) @@ -0,0 +1,8 @@ +# Remapper of standalone modules +# +# This file takes the old name (full package path) followed by spaces and/or +# tabs, followed by the new library name (full package path). As you can see; +# whitespace and hashes are ignored. + +#StringIO stringio +cStringIO cstringio Index: Lib/oldlib/__init__.py =================================================================== --- Lib/oldlib/__init__.py (revision 0) +++ Lib/oldlib/__init__.py (revision 0) @@ -0,0 +1,144 @@ +# Support for remapping old standard library module names to a new reorganized +# structure introduced in Python 2.6. Python 3.0 will permanently remove the +# old package and module names. +# +# This class works by hooking into the import machinery. When it starts up, +# it reads .mv data files (evocative of the Unix 'mv' command) in the oldlib +# package. These files support a very simple syntax. All blank lines and +# lines that start with # are ignored. All other lines contain two whitespace +# separated package names where the first is the old name and the second is +# the new name. Both names are absolute import full package paths for the +# module to remap (e.g. 'StringIO' or 'email.mime.image'). For packages, it +# is not necessary to include a remapping of the package name if only its +# subpackages are being renamed. +# +# When an import request comes in for one of the old names, this class will +# instead import the module under its new name, but it will insert the module +# into sys.modules under both its old and new name. This way, all existing +# code that uses the old name will continue to work, but the new module names +# will also work. As each old module is moved, the documentation should be +# updated so that only the new names are provided (although some recognition +# of the old name may be acceptable). When a module is rewritten or otherwise +# significantly updated, it should be rewritten to use the new names. +# +# This class is loaded during Python initializationi time by the import +# machinery. The OldStdlibLoader class is instantiated by that C code and +# placed on several path hook variables. See PEP 302 for a description of the +# interface this class implements and why it uses the hooks it uses. + +"""Remapper of Python 2.x module names to Python 3.x module names. + +The OldStdlibLoader class provided here should only be instantiated by +Python. Don't create one yourself. Instead, access the system's remapper +through the sys.stdlib_remapper attribute. +""" + +import os +import sys + +_magic = '' + + +class OldStdlibLoader(object): + def __init__(self): + # The mapping from old names to new names + self._packages = {} + # Read all the .mv files in our directory + self.read_directory_mv_files(os.path.dirname(__file__)) + # Hack the importer cache, meta_path, and sys.path so that the PEP 302 + # methods find_module() and load_module() get called at the right + # times. When the import machinery gets to the end of sys.path + # without being able to import the given module, Python will see the + # magic '' string and look to path_importer_cache to find a + # loader for that magic path. It will find our instance, calling + # first find_module() and -- if we are managing the name through the + # aliases -- call our load_module() method. That does the actual fun + # stuff of loading the new thing and inserting the resulting module in + # sys.modules. + # + # The one extra trick here is to also add ourselves to sys.meta_path. + # This is necessary for sub-package imports, so that when a module + # inside the package gets loaded, we'll be able to add to that + # package's __path__ variable to find this loader instance. Without + # this additional hook, if a package is not managed by us + # (e.g. because the package isn't being renamed), we'll never see + # import requests for modules inside the package. + sys.path_importer_cache[_magic] = self + sys.path.append(_magic) + sys.meta_path.append(self) + + def read_mv_file(self, filename): + """Open and read 'filename' for stdlib remappings. + + In the file named by 'filename', blank lines and lines that start with + a # are ignored. All other lines contain two whitespace separated + fields. The first field is the full dotted-path old module name and + the second field is the full dotted-path new module name. All + remappings in the named file are registered. + """ + with open(filename) as fp: + self._parse(fp) + + def read_directory_mv_files(self, dirname, suffix='.mv'): + """Parse the remappings in all .mv files inside 'dirname'. + + This method lists the given directory, then opens and reads all .mv + files in that directory, registering all module remappings found. + Optional 'suffix' can specific a remapping file suffix other than .mv. + """ + for fn in os.listdir(dirname): + if os.path.splitext(fn)[1] == suffix: + self.read_mv_file(os.path.join(dirname, fn)) + + def set_mapping(self, oldname, newname): + """Register additional module renamings. + + 'oldname' is the full dotted-path old name for the module. 'newname' + is the full dotted-path new name for the module. If a mapping for + 'oldname' already exists, it will be replaced. Use get_mapping() to + see if a mapping for oldname already exists. + + If newname is None, then the oldname remapping is removed if it + exists. No error is raised if the mapping does not exist. + """ + if newname is None and oldname in self._packages: + del self._packages[oldname] + else: + self._packages[oldname] = newname + + def get_mapping(self, oldname, default=None): + """Return the new module name for oldname, if it exists. + + If there is no existing mapping for oldname, 'default' is returned + (defaults to None). Both 'oldname' and the returned mapping value + must be the full dotted-path module name. + """ + return self._packages.get(oldname, default) + + def _parse(self, fp): + for line in fp: + line = line.strip() + if not line or line.startswith('#'): + continue + oldname, newname = line.split(None, 1) + self._packages[oldname] = newname + + def find_module(self, oldname, path=None): + """PEP 302 API.""" + # Get this loader instance on the package's __path__ so that + # sub-modules are properly handled. + if isinstance(path, list) and self not in path: + path.append(self) + # Can I handle this oldname? If so, return self. Otherwise, return + # None to let something else handle it. + return (self if oldname in self._packages else None) + + def load_module(self, oldname): + """PEP 302 API.""" + # Do the import and return the module + newname = self._packages[oldname] + # level=0 means absolute import + __import__(newname, level=0) + module = sys.modules[newname] + sys.modules[oldname] = module + return module Index: Lib/email/__init__.py =================================================================== --- Lib/email/__init__.py (revision 54146) +++ Lib/email/__init__.py (working copy) @@ -1,10 +1,10 @@ -# Copyright (C) 2001-2006 Python Software Foundation +# Copyright (C) 2001-2007 Python Software Foundation # Author: Barry Warsaw # Contact: email-sig@python.org """A package for parsing, handling, and generating email messages.""" -__version__ = '4.0.1' +__version__ = '4.1.0' __all__ = [ # Old names @@ -67,57 +67,7 @@ -# Lazy loading to provide name mapping from new-style names (PEP 8 compatible -# email 4.0 module names), to old-style names (email 3.0 module names). +# Expose old names -- XXX This should be automatic. +import os import sys - -class LazyImporter(object): - def __init__(self, module_name): - self.__name__ = 'email.' + module_name - - def __getattr__(self, name): - __import__(self.__name__) - mod = sys.modules[self.__name__] - self.__dict__.update(mod.__dict__) - return getattr(mod, name) - - -_LOWERNAMES = [ - # email. -> email. - 'Charset', - 'Encoders', - 'Errors', - 'FeedParser', - 'Generator', - 'Header', - 'Iterators', - 'Message', - 'Parser', - 'Utils', - 'base64MIME', - 'quopriMIME', - ] - -_MIMENAMES = [ - # email.MIME -> email.mime. - 'Audio', - 'Base', - 'Image', - 'Message', - 'Multipart', - 'NonMultipart', - 'Text', - ] - -for _name in _LOWERNAMES: - importer = LazyImporter(_name.lower()) - sys.modules['email.' + _name] = importer - setattr(sys.modules['email'], _name, importer) - - -import email.mime -for _name in _MIMENAMES: - importer = LazyImporter('mime.' + _name.lower()) - sys.modules['email.MIME' + _name] = importer - setattr(sys.modules['email'], 'MIME' + _name, importer) - setattr(sys.modules['email.mime'], _name, importer) +sys.stdlib_remapper.read_directory_mv_files(os.path.dirname(__file__)) Index: Lib/email/email.mv =================================================================== --- Lib/email/email.mv (revision 0) +++ Lib/email/email.mv (revision 0) @@ -0,0 +1,24 @@ +# Aliases for the email 3.x package names to the email 4.x package names. +# This was supported in a hacky and unique way in Python 2.5, but now we do it +# in the standard way. +email.Charset email.charset +email.Encoders email.encoders +email.Errors email.errors +email.FeedParser email.feedparser +email.Generator email.generator +email.Header email.header +email.Iterators email.iterators +email.Message email.message +email.Parser email.parser +email.Utils email.utils +email.base64MIME email.base64mime +email.quopriMIME email.quoprimime + +# MIME sub-modules +email.MIMEAudio email.mime.audio +email.MIMEBase email.mime.base +email.MIMEImage email.mime.image +email.MIMEMessage email.mime.message +email.MIMEMultipart email.mime.multipart +email.MIMENonMultipart email.mime.nonmultipart +email.MIMEText email.mime.text Index: Lib/test/test_oldlib.py =================================================================== --- Lib/test/test_oldlib.py (revision 0) +++ Lib/test/test_oldlib.py (revision 0) @@ -0,0 +1,74 @@ +import os +import sys +import tempfile +import unittest + +from contextlib import contextmanager +from test import test_support + +missing = object() + +@contextmanager +def mvfile(): + fd, fn = tempfile.mkstemp(suffix='.mv') + os.close(fd) + try: + yield fn + finally: + os.remove(fn) + + + +class TestStdlibRemapper(unittest.TestCase): + def _test(self, oldname, newname): + __import__(oldname) + __import__(newname) + self.failUnless(sys.modules[oldname] is sys.modules[newname]) + + def test_remap_toplevel(self): + self._test('cStringIO', 'cstringio') + + def test_remap_submodule(self): + self._test('email.MIMEText', 'email.mime.text') + + def test_programmatic_api(self): + eq = self.assertEqual + eq(sys.stdlib_remapper.get_mapping('dummy'), None) + eq(sys.stdlib_remapper.get_mapping('dummy', missing), missing) + sys.stdlib_remapper.set_mapping('dummy', 'doesnotexist') + eq(sys.stdlib_remapper.get_mapping('dummy'), 'doesnotexist') + sys.stdlib_remapper.set_mapping('dummy', None) + eq(sys.stdlib_remapper.get_mapping('dummy', missing), missing) + # Now try to actually import a programmatically remapped module + sys.stdlib_remapper.set_mapping('sys', 'syster') + sys.stdlib_remapper.set_mapping('sys', None) + + def test_file_api(self): + eq = self.assertEqual + with mvfile() as fn: + with open(fn, 'w') as fp: + print >> fp, 'jeremy', 'salty' + print >> fp, 'sys', 'sass' + sys.stdlib_remapper.read_mv_file(fn) + eq(sys.stdlib_remapper.get_mapping('jeremy'), 'salty') + sys.stdlib_remapper.set_mapping('sys', None) + sys.stdlib_remapper.set_mapping('jeremy', None) + eq(sys.stdlib_remapper.get_mapping('jeremy'), None) + # Read from a directory + sys.stdlib_remapper.read_directory_mv_files(os.path.dirname(fn)) + eq(sys.stdlib_remapper.get_mapping('jeremy'), 'salty') + + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestStdlibRemapper)) + return suite + + +def test_main(): + test_support.run_suite(suite()) + + +if __name__ == '__main__': + unittest.main(defaultTest='suite') Index: Modules/cStringIO.c =================================================================== --- Modules/cStringIO.c (revision 54146) +++ Modules/cStringIO.c (working copy) @@ -15,7 +15,7 @@ "\n" "Usage:\n" "\n" -" from cStringIO import StringIO\n" +" from cstringio import StringIO\n" "\n" " an_output_stream=StringIO()\n" " an_output_stream.write(some_stuff)\n" @@ -508,7 +508,7 @@ static PyTypeObject Otype = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ - "cStringIO.StringO", /*tp_name*/ + "cstringio.StringO", /*tp_name*/ sizeof(Oobject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ @@ -629,7 +629,7 @@ static PyTypeObject Itype = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ - "cStringIO.StringI", /*tp_name*/ + "cstringio.StringI", /*tp_name*/ sizeof(Iobject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ @@ -707,7 +707,7 @@ }; -/* Initialization function for the module (*must* be called initcStringIO) */ +/* Initialization function for the module (*must* be called initcstringio) */ static struct PycStringIO_CAPI CAPI = { IO_cread, @@ -724,12 +724,12 @@ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC -initcStringIO(void) { +initcstringio(void) { PyObject *m, *d, *v; /* Create the module and add the functions */ - m = Py_InitModule4("cStringIO", IO_methods, + m = Py_InitModule4("cstringio", IO_methods, cStringIO_module_documentation, (PyObject*)NULL,PYTHON_API_VERSION); if (m == NULL) return;