[Python-checkins] gh-104773: PEP 594: Remove the pipes module (#104848)
vstinner
webhook-mailer at python.org
Wed May 24 07:11:37 EDT 2023
https://github.com/python/cpython/commit/a4b7e9d1f812f2598ac9637d95e986c830bd451b
commit: a4b7e9d1f812f2598ac9637d95e986c830bd451b
branch: main
author: Victor Stinner <vstinner at python.org>
committer: vstinner <vstinner at python.org>
date: 2023-05-24T13:11:29+02:00
summary:
gh-104773: PEP 594: Remove the pipes module (#104848)
files:
A Misc/NEWS.d/next/Library/2023-05-24-11-45-22.gh-issue-104773.R0Br4-.rst
D Doc/library/pipes.rst
D Lib/pipes.py
D Lib/test/test_pipes.py
M Doc/library/superseded.rst
M Doc/whatsnew/3.11.rst
M Doc/whatsnew/3.12.rst
M Doc/whatsnew/3.13.rst
M Doc/whatsnew/3.3.rst
M Python/stdlib_module_names.h
diff --git a/Doc/library/pipes.rst b/Doc/library/pipes.rst
deleted file mode 100644
index 471ae0dbc976..000000000000
--- a/Doc/library/pipes.rst
+++ /dev/null
@@ -1,103 +0,0 @@
-:mod:`pipes` --- Interface to shell pipelines
-=============================================
-
-.. module:: pipes
- :platform: Unix
- :synopsis: A Python interface to Unix shell pipelines.
- :deprecated:
-
-.. sectionauthor:: Moshe Zadka <moshez at zadka.site.co.il>
-
-**Source code:** :source:`Lib/pipes.py`
-
-.. deprecated-removed:: 3.11 3.13
- The :mod:`pipes` module is deprecated
- (see :pep:`PEP 594 <594#pipes>` for details).
- Please use the :mod:`subprocess` module instead.
-
---------------
-
-The :mod:`pipes` module defines a class to abstract the concept of a *pipeline*
---- a sequence of converters from one file to another.
-
-Because the module uses :program:`/bin/sh` command lines, a POSIX or compatible
-shell for :func:`os.system` and :func:`os.popen` is required.
-
-.. availability:: Unix, not VxWorks.
-
-The :mod:`pipes` module defines the following class:
-
-
-.. class:: Template()
-
- An abstraction of a pipeline.
-
-Example::
-
- >>> import pipes
- >>> t = pipes.Template()
- >>> t.append('tr a-z A-Z', '--')
- >>> f = t.open('pipefile', 'w')
- >>> f.write('hello world')
- >>> f.close()
- >>> open('pipefile').read()
- 'HELLO WORLD'
-
-
-.. _template-objects:
-
-Template Objects
-----------------
-
-Template objects following methods:
-
-
-.. method:: Template.reset()
-
- Restore a pipeline template to its initial state.
-
-
-.. method:: Template.clone()
-
- Return a new, equivalent, pipeline template.
-
-
-.. method:: Template.debug(flag)
-
- If *flag* is true, turn debugging on. Otherwise, turn debugging off. When
- debugging is on, commands to be executed are printed, and the shell is given
- ``set -x`` command to be more verbose.
-
-
-.. method:: Template.append(cmd, kind)
-
- Append a new action at the end. The *cmd* variable must be a valid bourne shell
- command. The *kind* variable consists of two letters.
-
- The first letter can be either of ``'-'`` (which means the command reads its
- standard input), ``'f'`` (which means the commands reads a given file on the
- command line) or ``'.'`` (which means the commands reads no input, and hence
- must be first.)
-
- Similarly, the second letter can be either of ``'-'`` (which means the command
- writes to standard output), ``'f'`` (which means the command writes a file on
- the command line) or ``'.'`` (which means the command does not write anything,
- and hence must be last.)
-
-
-.. method:: Template.prepend(cmd, kind)
-
- Add a new action at the beginning. See :meth:`append` for explanations of the
- arguments.
-
-
-.. method:: Template.open(file, mode)
-
- Return a file-like object, open to *file*, but read from or written to by the
- pipeline. Note that only one of ``'r'``, ``'w'`` may be given.
-
-
-.. method:: Template.copy(infile, outfile)
-
- Copy *infile* to *outfile* through the pipe.
-
diff --git a/Doc/library/superseded.rst b/Doc/library/superseded.rst
index cad4e9c50b69..0153b4550558 100644
--- a/Doc/library/superseded.rst
+++ b/Doc/library/superseded.rst
@@ -21,7 +21,6 @@ backwards compatibility. They have been superseded by other modules.
nntplib.rst
optparse.rst
ossaudiodev.rst
- pipes.rst
spwd.rst
sunau.rst
uu.rst
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index 4ad68463e0b5..778ab28419ce 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -1731,7 +1731,7 @@ Modules
slated for removal in Python 3.13:
+---------------------+---------------------+---------------------+---------------------+---------------------+
- | :mod:`aifc` | :mod:`chunk` | :mod:`msilib` | :mod:`pipes` | :mod:`!telnetlib` |
+ | :mod:`aifc` | :mod:`chunk` | :mod:`msilib` | :mod:`!pipes` | :mod:`!telnetlib` |
+---------------------+---------------------+---------------------+---------------------+---------------------+
| :mod:`audioop` | :mod:`crypt` | :mod:`nis` | :mod:`!sndhdr` | :mod:`uu` |
+---------------------+---------------------+---------------------+---------------------+---------------------+
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index 9cf10196dcfc..62050aa01461 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -898,7 +898,7 @@ Modules (see :pep:`594`):
* :mod:`nis`
* :mod:`nntplib`
* :mod:`ossaudiodev`
-* :mod:`pipes`
+* :mod:`!pipes`
* :mod:`!sndhdr`
* :mod:`spwd`
* :mod:`sunau`
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index 1d4a4ca9614c..a5549f4470d2 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -153,6 +153,10 @@ Removed
<https://pypi.org/project/python-magic/>`_ instead.
(Contributed by Victor Stinner in :gh:`104773`.)
+* :pep:`594`: Remove the :mod:`!pipes` module, deprecated in Python 3.11:
+ use the :mod:`subprocess` module instead.
+ (Contributed by Victor Stinner in :gh:`104773`.)
+
Porting to Python 3.13
======================
diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst
index f121652ba51c..c05b8e5ef753 100644
--- a/Doc/whatsnew/3.3.rst
+++ b/Doc/whatsnew/3.3.rst
@@ -1778,7 +1778,7 @@ shlex
-----
The previously undocumented helper function ``quote`` from the
-:mod:`pipes` modules has been moved to the :mod:`shlex` module and
+:mod:`!pipes` modules has been moved to the :mod:`shlex` module and
documented. :func:`~shlex.quote` properly escapes all characters in a string
that might be otherwise given special meaning by the shell.
diff --git a/Lib/pipes.py b/Lib/pipes.py
deleted file mode 100644
index 61d63b48d3e4..000000000000
--- a/Lib/pipes.py
+++ /dev/null
@@ -1,250 +0,0 @@
-"""Conversion pipeline templates.
-
-The problem:
-------------
-
-Suppose you have some data that you want to convert to another format,
-such as from GIF image format to PPM image format. Maybe the
-conversion involves several steps (e.g. piping it through compress or
-uuencode). Some of the conversion steps may require that their input
-is a disk file, others may be able to read standard input; similar for
-their output. The input to the entire conversion may also be read
-from a disk file or from an open file, and similar for its output.
-
-The module lets you construct a pipeline template by sticking one or
-more conversion steps together. It will take care of creating and
-removing temporary files if they are necessary to hold intermediate
-data. You can then use the template to do conversions from many
-different sources to many different destinations. The temporary
-file names used are different each time the template is used.
-
-The templates are objects so you can create templates for many
-different conversion steps and store them in a dictionary, for
-instance.
-
-
-Directions:
------------
-
-To create a template:
- t = Template()
-
-To add a conversion step to a template:
- t.append(command, kind)
-where kind is a string of two characters: the first is '-' if the
-command reads its standard input or 'f' if it requires a file; the
-second likewise for the output. The command must be valid /bin/sh
-syntax. If input or output files are required, they are passed as
-$IN and $OUT; otherwise, it must be possible to use the command in
-a pipeline.
-
-To add a conversion step at the beginning:
- t.prepend(command, kind)
-
-To convert a file to another file using a template:
- sts = t.copy(infile, outfile)
-If infile or outfile are the empty string, standard input is read or
-standard output is written, respectively. The return value is the
-exit status of the conversion pipeline.
-
-To open a file for reading or writing through a conversion pipeline:
- fp = t.open(file, mode)
-where mode is 'r' to read the file, or 'w' to write it -- just like
-for the built-in function open() or for os.popen().
-
-To create a new template object initialized to a given one:
- t2 = t.clone()
-""" # '
-
-
-import re
-import os
-import tempfile
-import warnings
-# we import the quote function rather than the module for backward compat
-# (quote used to be an undocumented but used function in pipes)
-from shlex import quote
-
-warnings._deprecated(__name__, remove=(3, 13))
-
-__all__ = ["Template"]
-
-# Conversion step kinds
-
-FILEIN_FILEOUT = 'ff' # Must read & write real files
-STDIN_FILEOUT = '-f' # Must write a real file
-FILEIN_STDOUT = 'f-' # Must read a real file
-STDIN_STDOUT = '--' # Normal pipeline element
-SOURCE = '.-' # Must be first, writes stdout
-SINK = '-.' # Must be last, reads stdin
-
-stepkinds = [FILEIN_FILEOUT, STDIN_FILEOUT, FILEIN_STDOUT, STDIN_STDOUT, \
- SOURCE, SINK]
-
-
-class Template:
- """Class representing a pipeline template."""
-
- def __init__(self):
- """Template() returns a fresh pipeline template."""
- self.debugging = 0
- self.reset()
-
- def __repr__(self):
- """t.__repr__() implements repr(t)."""
- return '<Template instance, steps=%r>' % (self.steps,)
-
- def reset(self):
- """t.reset() restores a pipeline template to its initial state."""
- self.steps = []
-
- def clone(self):
- """t.clone() returns a new pipeline template with identical
- initial state as the current one."""
- t = Template()
- t.steps = self.steps[:]
- t.debugging = self.debugging
- return t
-
- def debug(self, flag):
- """t.debug(flag) turns debugging on or off."""
- self.debugging = flag
-
- def append(self, cmd, kind):
- """t.append(cmd, kind) adds a new step at the end."""
- if not isinstance(cmd, str):
- raise TypeError('Template.append: cmd must be a string')
- if kind not in stepkinds:
- raise ValueError('Template.append: bad kind %r' % (kind,))
- if kind == SOURCE:
- raise ValueError('Template.append: SOURCE can only be prepended')
- if self.steps and self.steps[-1][1] == SINK:
- raise ValueError('Template.append: already ends with SINK')
- if kind[0] == 'f' and not re.search(r'\$IN\b', cmd):
- raise ValueError('Template.append: missing $IN in cmd')
- if kind[1] == 'f' and not re.search(r'\$OUT\b', cmd):
- raise ValueError('Template.append: missing $OUT in cmd')
- self.steps.append((cmd, kind))
-
- def prepend(self, cmd, kind):
- """t.prepend(cmd, kind) adds a new step at the front."""
- if not isinstance(cmd, str):
- raise TypeError('Template.prepend: cmd must be a string')
- if kind not in stepkinds:
- raise ValueError('Template.prepend: bad kind %r' % (kind,))
- if kind == SINK:
- raise ValueError('Template.prepend: SINK can only be appended')
- if self.steps and self.steps[0][1] == SOURCE:
- raise ValueError('Template.prepend: already begins with SOURCE')
- if kind[0] == 'f' and not re.search(r'\$IN\b', cmd):
- raise ValueError('Template.prepend: missing $IN in cmd')
- if kind[1] == 'f' and not re.search(r'\$OUT\b', cmd):
- raise ValueError('Template.prepend: missing $OUT in cmd')
- self.steps.insert(0, (cmd, kind))
-
- def open(self, file, rw):
- """t.open(file, rw) returns a pipe or file object open for
- reading or writing; the file is the other end of the pipeline."""
- if rw == 'r':
- return self.open_r(file)
- if rw == 'w':
- return self.open_w(file)
- raise ValueError('Template.open: rw must be \'r\' or \'w\', not %r'
- % (rw,))
-
- def open_r(self, file):
- """t.open_r(file) and t.open_w(file) implement
- t.open(file, 'r') and t.open(file, 'w') respectively."""
- if not self.steps:
- return open(file, 'r')
- if self.steps[-1][1] == SINK:
- raise ValueError('Template.open_r: pipeline ends width SINK')
- cmd = self.makepipeline(file, '')
- return os.popen(cmd, 'r')
-
- def open_w(self, file):
- if not self.steps:
- return open(file, 'w')
- if self.steps[0][1] == SOURCE:
- raise ValueError('Template.open_w: pipeline begins with SOURCE')
- cmd = self.makepipeline('', file)
- return os.popen(cmd, 'w')
-
- def copy(self, infile, outfile):
- return os.system(self.makepipeline(infile, outfile))
-
- def makepipeline(self, infile, outfile):
- cmd = makepipeline(infile, self.steps, outfile)
- if self.debugging:
- print(cmd)
- cmd = 'set -x; ' + cmd
- return cmd
-
-
-def makepipeline(infile, steps, outfile):
- # Build a list with for each command:
- # [input filename or '', command string, kind, output filename or '']
-
- list = []
- for cmd, kind in steps:
- list.append(['', cmd, kind, ''])
- #
- # Make sure there is at least one step
- #
- if not list:
- list.append(['', 'cat', '--', ''])
- #
- # Take care of the input and output ends
- #
- [cmd, kind] = list[0][1:3]
- if kind[0] == 'f' and not infile:
- list.insert(0, ['', 'cat', '--', ''])
- list[0][0] = infile
- #
- [cmd, kind] = list[-1][1:3]
- if kind[1] == 'f' and not outfile:
- list.append(['', 'cat', '--', ''])
- list[-1][-1] = outfile
- #
- # Invent temporary files to connect stages that need files
- #
- garbage = []
- for i in range(1, len(list)):
- lkind = list[i-1][2]
- rkind = list[i][2]
- if lkind[1] == 'f' or rkind[0] == 'f':
- (fd, temp) = tempfile.mkstemp()
- os.close(fd)
- garbage.append(temp)
- list[i-1][-1] = list[i][0] = temp
- #
- for item in list:
- [inf, cmd, kind, outf] = item
- if kind[1] == 'f':
- cmd = 'OUT=' + quote(outf) + '; ' + cmd
- if kind[0] == 'f':
- cmd = 'IN=' + quote(inf) + '; ' + cmd
- if kind[0] == '-' and inf:
- cmd = cmd + ' <' + quote(inf)
- if kind[1] == '-' and outf:
- cmd = cmd + ' >' + quote(outf)
- item[1] = cmd
- #
- cmdlist = list[0][1]
- for item in list[1:]:
- [cmd, kind] = item[1:3]
- if item[0] == '':
- if 'f' in kind:
- cmd = '{ ' + cmd + '; }'
- cmdlist = cmdlist + ' |\n' + cmd
- else:
- cmdlist = cmdlist + '\n' + cmd
- #
- if garbage:
- rmcmd = 'rm -f'
- for file in garbage:
- rmcmd = rmcmd + ' ' + quote(file)
- trapcmd = 'trap ' + quote(rmcmd + '; exit') + ' 1 2 3 13 14 15'
- cmdlist = trapcmd + '\n' + cmdlist + '\n' + rmcmd
- #
- return cmdlist
diff --git a/Lib/test/test_pipes.py b/Lib/test/test_pipes.py
deleted file mode 100644
index 09e21153ec85..000000000000
--- a/Lib/test/test_pipes.py
+++ /dev/null
@@ -1,210 +0,0 @@
-import os
-import string
-import unittest
-import shutil
-from test.support import reap_children, unix_shell
-from test.support.os_helper import TESTFN, unlink
-from test.support.warnings_helper import import_deprecated
-
-pipes = import_deprecated("pipes")
-
-
-if os.name != 'posix':
- raise unittest.SkipTest('pipes module only works on posix')
-
-if not (unix_shell and os.path.exists(unix_shell)):
- raise unittest.SkipTest('pipes module requires a shell')
-
-TESTFN2 = TESTFN + "2"
-
-# tr a-z A-Z is not portable, so make the ranges explicit
-s_command = 'tr %s %s' % (string.ascii_lowercase, string.ascii_uppercase)
-
-class SimplePipeTests(unittest.TestCase):
- def tearDown(self):
- for f in (TESTFN, TESTFN2):
- unlink(f)
-
- def testSimplePipe1(self):
- if shutil.which('tr') is None:
- self.skipTest('tr is not available')
- t = pipes.Template()
- t.append(s_command, pipes.STDIN_STDOUT)
- with t.open(TESTFN, 'w') as f:
- f.write('hello world #1')
- with open(TESTFN) as f:
- self.assertEqual(f.read(), 'HELLO WORLD #1')
-
- def testSimplePipe2(self):
- if shutil.which('tr') is None:
- self.skipTest('tr is not available')
- with open(TESTFN, 'w') as f:
- f.write('hello world #2')
- t = pipes.Template()
- t.append(s_command + ' < $IN > $OUT', pipes.FILEIN_FILEOUT)
- t.copy(TESTFN, TESTFN2)
- with open(TESTFN2) as f:
- self.assertEqual(f.read(), 'HELLO WORLD #2')
-
- def testSimplePipe3(self):
- if shutil.which('tr') is None:
- self.skipTest('tr is not available')
- with open(TESTFN, 'w') as f:
- f.write('hello world #2')
- t = pipes.Template()
- t.append(s_command + ' < $IN', pipes.FILEIN_STDOUT)
- f = t.open(TESTFN, 'r')
- try:
- self.assertEqual(f.read(), 'HELLO WORLD #2')
- finally:
- f.close()
-
- def testEmptyPipeline1(self):
- # copy through empty pipe
- d = 'empty pipeline test COPY'
- with open(TESTFN, 'w') as f:
- f.write(d)
- with open(TESTFN2, 'w') as f:
- f.write('')
- t=pipes.Template()
- t.copy(TESTFN, TESTFN2)
- with open(TESTFN2) as f:
- self.assertEqual(f.read(), d)
-
- def testEmptyPipeline2(self):
- # read through empty pipe
- d = 'empty pipeline test READ'
- with open(TESTFN, 'w') as f:
- f.write(d)
- t=pipes.Template()
- f = t.open(TESTFN, 'r')
- try:
- self.assertEqual(f.read(), d)
- finally:
- f.close()
-
- def testEmptyPipeline3(self):
- # write through empty pipe
- d = 'empty pipeline test WRITE'
- t = pipes.Template()
- with t.open(TESTFN, 'w') as f:
- f.write(d)
- with open(TESTFN) as f:
- self.assertEqual(f.read(), d)
-
- def testRepr(self):
- t = pipes.Template()
- self.assertEqual(repr(t), "<Template instance, steps=[]>")
- t.append('tr a-z A-Z', pipes.STDIN_STDOUT)
- self.assertEqual(repr(t),
- "<Template instance, steps=[('tr a-z A-Z', '--')]>")
-
- def testSetDebug(self):
- t = pipes.Template()
- t.debug(False)
- self.assertEqual(t.debugging, False)
- t.debug(True)
- self.assertEqual(t.debugging, True)
-
- def testReadOpenSink(self):
- # check calling open('r') on a pipe ending with
- # a sink raises ValueError
- t = pipes.Template()
- t.append('boguscmd', pipes.SINK)
- self.assertRaises(ValueError, t.open, 'bogusfile', 'r')
-
- def testWriteOpenSource(self):
- # check calling open('w') on a pipe ending with
- # a source raises ValueError
- t = pipes.Template()
- t.prepend('boguscmd', pipes.SOURCE)
- self.assertRaises(ValueError, t.open, 'bogusfile', 'w')
-
- def testBadAppendOptions(self):
- t = pipes.Template()
-
- # try a non-string command
- self.assertRaises(TypeError, t.append, 7, pipes.STDIN_STDOUT)
-
- # try a type that isn't recognized
- self.assertRaises(ValueError, t.append, 'boguscmd', 'xx')
-
- # shouldn't be able to append a source
- self.assertRaises(ValueError, t.append, 'boguscmd', pipes.SOURCE)
-
- # check appending two sinks
- t = pipes.Template()
- t.append('boguscmd', pipes.SINK)
- self.assertRaises(ValueError, t.append, 'boguscmd', pipes.SINK)
-
- # command needing file input but with no $IN
- t = pipes.Template()
- self.assertRaises(ValueError, t.append, 'boguscmd $OUT',
- pipes.FILEIN_FILEOUT)
- t = pipes.Template()
- self.assertRaises(ValueError, t.append, 'boguscmd',
- pipes.FILEIN_STDOUT)
-
- # command needing file output but with no $OUT
- t = pipes.Template()
- self.assertRaises(ValueError, t.append, 'boguscmd $IN',
- pipes.FILEIN_FILEOUT)
- t = pipes.Template()
- self.assertRaises(ValueError, t.append, 'boguscmd',
- pipes.STDIN_FILEOUT)
-
-
- def testBadPrependOptions(self):
- t = pipes.Template()
-
- # try a non-string command
- self.assertRaises(TypeError, t.prepend, 7, pipes.STDIN_STDOUT)
-
- # try a type that isn't recognized
- self.assertRaises(ValueError, t.prepend, 'tr a-z A-Z', 'xx')
-
- # shouldn't be able to prepend a sink
- self.assertRaises(ValueError, t.prepend, 'boguscmd', pipes.SINK)
-
- # check prepending two sources
- t = pipes.Template()
- t.prepend('boguscmd', pipes.SOURCE)
- self.assertRaises(ValueError, t.prepend, 'boguscmd', pipes.SOURCE)
-
- # command needing file input but with no $IN
- t = pipes.Template()
- self.assertRaises(ValueError, t.prepend, 'boguscmd $OUT',
- pipes.FILEIN_FILEOUT)
- t = pipes.Template()
- self.assertRaises(ValueError, t.prepend, 'boguscmd',
- pipes.FILEIN_STDOUT)
-
- # command needing file output but with no $OUT
- t = pipes.Template()
- self.assertRaises(ValueError, t.prepend, 'boguscmd $IN',
- pipes.FILEIN_FILEOUT)
- t = pipes.Template()
- self.assertRaises(ValueError, t.prepend, 'boguscmd',
- pipes.STDIN_FILEOUT)
-
- def testBadOpenMode(self):
- t = pipes.Template()
- self.assertRaises(ValueError, t.open, 'bogusfile', 'x')
-
- def testClone(self):
- t = pipes.Template()
- t.append('tr a-z A-Z', pipes.STDIN_STDOUT)
-
- u = t.clone()
- self.assertNotEqual(id(t), id(u))
- self.assertEqual(t.steps, u.steps)
- self.assertNotEqual(id(t.steps), id(u.steps))
- self.assertEqual(t.debugging, u.debugging)
-
-
-def tearDownModule():
- reap_children()
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2023-05-24-11-45-22.gh-issue-104773.R0Br4-.rst b/Misc/NEWS.d/next/Library/2023-05-24-11-45-22.gh-issue-104773.R0Br4-.rst
new file mode 100644
index 000000000000..31da29ec2a59
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-05-24-11-45-22.gh-issue-104773.R0Br4-.rst
@@ -0,0 +1,2 @@
+:pep:`594`: Remove the :mod:`!pipes` module, deprecated in Python 3.11.
+Patch by Victor Stinner.
diff --git a/Python/stdlib_module_names.h b/Python/stdlib_module_names.h
index 245c3a1645f3..c82512f43c7e 100644
--- a/Python/stdlib_module_names.h
+++ b/Python/stdlib_module_names.h
@@ -200,7 +200,6 @@ static const char* _Py_stdlib_module_names[] = {
"pdb",
"pickle",
"pickletools",
-"pipes",
"pkgutil",
"platform",
"plistlib",
More information about the Python-checkins
mailing list