diff options
author | Victor Stinner <vstinner@python.org> | 2023-05-24 11:11:29 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-24 11:11:29 (GMT) |
commit | a4b7e9d1f812f2598ac9637d95e986c830bd451b (patch) | |
tree | 1d97192f751efd22ba4fe4ac1131becd23e24cd8 | |
parent | 3e97c001711ab68e3d54d65e264cb5c37fefbec0 (diff) | |
download | cpython-a4b7e9d1f812f2598ac9637d95e986c830bd451b.zip cpython-a4b7e9d1f812f2598ac9637d95e986c830bd451b.tar.gz cpython-a4b7e9d1f812f2598ac9637d95e986c830bd451b.tar.bz2 |
gh-104773: PEP 594: Remove the pipes module (#104848)
-rw-r--r-- | Doc/library/pipes.rst | 103 | ||||
-rw-r--r-- | Doc/library/superseded.rst | 1 | ||||
-rw-r--r-- | Doc/whatsnew/3.11.rst | 2 | ||||
-rw-r--r-- | Doc/whatsnew/3.12.rst | 2 | ||||
-rw-r--r-- | Doc/whatsnew/3.13.rst | 4 | ||||
-rw-r--r-- | Doc/whatsnew/3.3.rst | 2 | ||||
-rw-r--r-- | Lib/pipes.py | 250 | ||||
-rw-r--r-- | Lib/test/test_pipes.py | 210 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2023-05-24-11-45-22.gh-issue-104773.R0Br4-.rst | 2 | ||||
-rw-r--r-- | Python/stdlib_module_names.h | 1 |
10 files changed, 9 insertions, 568 deletions
diff --git a/Doc/library/pipes.rst b/Doc/library/pipes.rst deleted file mode 100644 index 471ae0d..0000000 --- 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@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 cad4e9c..0153b45 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 4ad6846..778ab28 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 9cf1019..62050aa 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 1d4a4ca..a5549f4 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 f121652..c05b8e5 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 61d63b4..0000000 --- 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 09e2115..0000000 --- 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 0000000..31da29e --- /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 245c3a1..c82512f 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", |