summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib')
-rw-r--r--Lib/compileall.py14
-rw-r--r--Lib/distutils/tests/support.py7
-rw-r--r--Lib/packaging/command/build_py.py2
-rw-r--r--Lib/packaging/command/install_lib.py2
-rw-r--r--Lib/packaging/command/register.py1
-rw-r--r--Lib/packaging/command/upload.py1
-rw-r--r--Lib/packaging/config.py31
-rw-r--r--Lib/packaging/create.py6
-rw-r--r--Lib/packaging/tests/support.py40
-rw-r--r--Lib/packaging/tests/test_config.py47
-rw-r--r--Lib/packaging/tests/test_util.py9
-rw-r--r--Lib/packaging/util.py163
-rw-r--r--Lib/pipes.py2
-rw-r--r--Lib/shutil.py3
14 files changed, 145 insertions, 183 deletions
diff --git a/Lib/compileall.py b/Lib/compileall.py
index 743be27..d3cff6a 100644
--- a/Lib/compileall.py
+++ b/Lib/compileall.py
@@ -142,7 +142,7 @@ def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=False,
Arguments (all optional):
- skip_curdir: if true, skip current directory (default true)
+ skip_curdir: if true, skip current directory (default True)
maxlevels: max recursion level (default 0)
force: as for compile_dir() (default False)
quiet: as for compile_dir() (default False)
@@ -177,17 +177,17 @@ def main():
help='use legacy (pre-PEP3147) compiled file locations')
parser.add_argument('-d', metavar='DESTDIR', dest='ddir', default=None,
help=('directory to prepend to file paths for use in '
- 'compile time tracebacks and in runtime '
+ 'compile-time tracebacks and in runtime '
'tracebacks in cases where the source file is '
'unavailable'))
parser.add_argument('-x', metavar='REGEXP', dest='rx', default=None,
- help=('skip files matching the regular expression. '
- 'The regexp is searched for in the full path '
- 'to each file considered for compilation.'))
+ help=('skip files matching the regular expression; '
+ 'the regexp is searched for in the full path '
+ 'of each file considered for compilation'))
parser.add_argument('-i', metavar='FILE', dest='flist',
help=('add all the files and directories listed in '
- 'FILE to the list considered for compilation. '
- 'If "-", names are read from stdin.'))
+ 'FILE to the list considered for compilation; '
+ 'if "-", names are read from stdin'))
parser.add_argument('compile_dest', metavar='FILE|DIR', nargs='*',
help=('zero or more file and directory names '
'to compile; if no arguments given, defaults '
diff --git a/Lib/distutils/tests/support.py b/Lib/distutils/tests/support.py
index 44fcd6b..d77bbee 100644
--- a/Lib/distutils/tests/support.py
+++ b/Lib/distutils/tests/support.py
@@ -175,10 +175,9 @@ def _get_xxmodule_path():
def fixup_build_ext(cmd):
"""Function needed to make build_ext tests pass.
- When Python was build with --enable-shared on Unix, -L. is not good
- enough to find the libpython<blah>.so. This is because regrtest runs
- it under a tempdir, not in the top level where the .so lives. By the
- time we've gotten here, Python's already been chdir'd to the tempdir.
+ When Python was built with --enable-shared on Unix, -L. is not enough to
+ find libpython<blah>.so, because regrtest runs in a tempdir, not in the
+ source directory where the .so lives.
When Python was built with in debug mode on Windows, build_ext commands
need their debug attribute set, and it is not done automatically for
diff --git a/Lib/packaging/command/build_py.py b/Lib/packaging/command/build_py.py
index 360f4c9..7baa6e4 100644
--- a/Lib/packaging/command/build_py.py
+++ b/Lib/packaging/command/build_py.py
@@ -393,7 +393,7 @@ class build_py(Command, Mixin2to3):
self.get_command_name())
return
- from packaging.util import byte_compile
+ from packaging.util import byte_compile # FIXME use compileall
prefix = self.build_lib
if prefix[-1] != os.sep:
prefix = prefix + os.sep
diff --git a/Lib/packaging/command/install_lib.py b/Lib/packaging/command/install_lib.py
index 5ff9cee..978f0ef 100644
--- a/Lib/packaging/command/install_lib.py
+++ b/Lib/packaging/command/install_lib.py
@@ -122,7 +122,7 @@ class install_lib(Command):
self.get_command_name())
return
- from packaging.util import byte_compile
+ from packaging.util import byte_compile # FIXME use compileall
# Get the "--root" directory supplied to the "install_dist" command,
# and use it as a prefix to strip off the purported filename
diff --git a/Lib/packaging/command/register.py b/Lib/packaging/command/register.py
index 006dfdf..67cda80 100644
--- a/Lib/packaging/command/register.py
+++ b/Lib/packaging/command/register.py
@@ -2,7 +2,6 @@
# Contributed by Richard Jones
-import io
import getpass
import urllib.error
import urllib.parse
diff --git a/Lib/packaging/command/upload.py b/Lib/packaging/command/upload.py
index e39016c..f56d2c6 100644
--- a/Lib/packaging/command/upload.py
+++ b/Lib/packaging/command/upload.py
@@ -5,7 +5,6 @@ import socket
import logging
import platform
import urllib.parse
-from io import BytesIO
from base64 import standard_b64encode
from hashlib import md5
from urllib.error import HTTPError
diff --git a/Lib/packaging/config.py b/Lib/packaging/config.py
index 43263f7..e02800e 100644
--- a/Lib/packaging/config.py
+++ b/Lib/packaging/config.py
@@ -16,6 +16,19 @@ from packaging.command import set_command
from packaging.markers import interpret
+def _check_name(name, packages):
+ if '.' not in name:
+ return
+ parts = name.split('.')
+ modname = parts[-1]
+ parent = '.'.join(parts[:-1])
+ if parent not in packages:
+ # we could log a warning instead of raising, but what's the use
+ # of letting people build modules they can't import?
+ raise PackagingOptionError(
+ 'parent package for extension %r not found' % name)
+
+
def _pop_values(values_dct, key):
"""Remove values from the dictionary and convert them as a list"""
vals_str = values_dct.pop(key, '')
@@ -142,7 +155,8 @@ class Config:
try:
hook = resolve_name(line)
except ImportError as e:
- logger.warning('cannot find setup hook: %s', e.args[0])
+ logger.warning('cannot find setup hook: %s',
+ e.args[0])
else:
self.setup_hooks.append(hook)
self.run_hooks(content)
@@ -216,7 +230,7 @@ class Config:
for data in files.get('package_data', []):
data = data.split('=')
if len(data) != 2:
- continue # XXX error should never pass silently
+ continue # FIXME errors should never pass silently
key, value = data
self.dist.package_data[key.strip()] = value.strip()
@@ -251,13 +265,18 @@ class Config:
ext_modules = self.dist.ext_modules
for section_key in content:
- labels = section_key.split('=')
+ # no str.partition in 2.4 :(
+ labels = section_key.split(':')
if len(labels) == 2 and labels[0] == 'extension':
- # labels[1] not used from now but should be implemented
- # for extension build dependency
values_dct = content[section_key]
+ if 'name' in values_dct:
+ raise PackagingOptionError(
+ 'extension name should be given as [extension: name], '
+ 'not as key')
+ name = labels[1].strip()
+ _check_name(name, self.dist.packages)
ext_modules.append(Extension(
- values_dct.pop('name'),
+ name,
_pop_values(values_dct, 'sources'),
_pop_values(values_dct, 'include_dirs'),
_pop_values(values_dct, 'define_macros'),
diff --git a/Lib/packaging/create.py b/Lib/packaging/create.py
index 8f39172..ecabca0 100644
--- a/Lib/packaging/create.py
+++ b/Lib/packaging/create.py
@@ -36,7 +36,7 @@ from packaging._trove import all_classifiers as _CLASSIFIERS_LIST
from packaging.version import is_valid_version
_FILENAME = 'setup.cfg'
-_DEFAULT_CFG = '.pypkgcreate'
+_DEFAULT_CFG = '.pypkgcreate' # FIXME use a section in user .pydistutils.cfg
_helptext = {
'name': '''
@@ -127,6 +127,10 @@ def ask_yn(question, default=None, helptext=None):
print('\nERROR: You must select "Y" or "N".\n')
+# XXX use util.ask
+# FIXME: if prompt ends with '?', don't add ':'
+
+
def ask(question, default=None, helptext=None, required=True,
lengthy=False, multiline=False):
prompt = '%s: ' % (question,)
diff --git a/Lib/packaging/tests/support.py b/Lib/packaging/tests/support.py
index df6195a..33d5966 100644
--- a/Lib/packaging/tests/support.py
+++ b/Lib/packaging/tests/support.py
@@ -1,5 +1,8 @@
"""Support code for packaging test cases.
+*This module should not be considered public: its content and API may
+change in incompatible ways.*
+
A few helper classes are provided: LoggingCatcher, TempdirManager and
EnvironRestorer. They are written to be used as mixins::
@@ -7,6 +10,7 @@ EnvironRestorer. They are written to be used as mixins::
from packaging.tests.support import LoggingCatcher
class SomeTestCase(LoggingCatcher, unittest.TestCase):
+ ...
If you need to define a setUp method on your test class, you have to
call the mixin class' setUp method or it won't work (same thing for
@@ -14,17 +18,18 @@ tearDown):
def setUp(self):
super(SomeTestCase, self).setUp()
- ... # other setup code
+ ... # other setup code
Also provided is a DummyCommand class, useful to mock commands in the
-tests of another command that needs them, a create_distribution function
-and a skip_unless_symlink decorator.
+tests of another command that needs them, for example to fake
+compilation in build_ext (this requires that the mock build_ext command
+be injected into the distribution object's command_obj dictionary).
-Also provided is a DummyCommand class, useful to mock commands in the
-tests of another command that needs them, a create_distribution function
-and a skip_unless_symlink decorator.
+For tests that need to compile an extension module, use the
+copy_xxmodule_c and fixup_build_ext functions.
Each class or function has a docstring to explain its purpose and usage.
+Existing tests should also be used as examples.
"""
import os
@@ -39,9 +44,17 @@ from packaging.dist import Distribution
from packaging.tests import unittest
from test.support import requires_zlib, unlink
-__all__ = ['LoggingCatcher', 'TempdirManager', 'EnvironRestorer',
- 'DummyCommand', 'unittest', 'create_distribution',
- 'skip_unless_symlink', 'requires_zlib', 'copy_xxmodule_c']
+# define __all__ to make pydoc more useful
+__all__ = [
+ # TestCase mixins
+ 'LoggingCatcher', 'TempdirManager', 'EnvironRestorer',
+ # mocks
+ 'DummyCommand', 'TestDistribution',
+ # misc. functions and decorators
+ 'fake_dec', 'create_distribution', 'copy_xxmodule_c', 'fixup_build_ext',
+ # imported from this module for backport purposes
+ 'unittest', 'requires_zlib', 'skip_unless_symlink',
+]
logger = logging.getLogger('packaging')
@@ -233,6 +246,8 @@ class DummyCommand:
Useful for mocking one dependency command in the tests for another
command, see e.g. the dummy build command in test_build_scripts.
"""
+ # XXX does not work with dist.get_reinitialized_command, which typechecks
+ # and wants a finalized attribute
def __init__(self, **kwargs):
for kw, val in kwargs.items():
@@ -308,10 +323,9 @@ def _get_xxmodule_path():
def fixup_build_ext(cmd):
"""Function needed to make build_ext tests pass.
- When Python was build with --enable-shared on Unix, -L. is not good
- enough to find the libpython<blah>.so. This is because regrtest runs
- it under a tempdir, not in the top level where the .so lives. By the
- time we've gotten here, Python's already been chdir'd to the tempdir.
+ When Python was built with --enable-shared on Unix, -L. is not enough to
+ find libpython<blah>.so, because regrtest runs in a tempdir, not in the
+ source directory where the .so lives.
When Python was built with in debug mode on Windows, build_ext commands
need their debug attribute set, and it is not done automatically for
diff --git a/Lib/packaging/tests/test_config.py b/Lib/packaging/tests/test_config.py
index 43ab2c8..bcb55fb 100644
--- a/Lib/packaging/tests/test_config.py
+++ b/Lib/packaging/tests/test_config.py
@@ -6,7 +6,7 @@ from io import StringIO
from packaging import command
from packaging.dist import Distribution
-from packaging.errors import PackagingFileError
+from packaging.errors import PackagingFileError, PackagingOptionError
from packaging.compiler import new_compiler, _COMPILERS
from packaging.command.sdist import sdist
@@ -100,21 +100,21 @@ sub_commands = foo
# Can not be merged with SETUP_CFG else install_dist
# command will fail when trying to compile C sources
+# TODO use a DummyCommand to mock build_ext
EXT_SETUP_CFG = """
[files]
packages = one
two
+ parent.undeclared
-[extension=speed_coconuts]
-name = one.speed_coconuts
+[extension:one.speed_coconuts]
sources = c_src/speed_coconuts.c
extra_link_args = "`gcc -print-file-name=libgcc.a`" -shared
define_macros = HAVE_CAIRO HAVE_GTK2
libraries = gecodeint gecodekernel -- sys.platform != 'win32'
GecodeInt GecodeKernel -- sys.platform == 'win32'
-[extension=fast_taunt]
-name = two.fast_taunt
+[extension: two.fast_taunt]
sources = cxx_src/utils_taunt.cxx
cxx_src/python_module.cxx
include_dirs = /usr/include/gecode
@@ -124,6 +124,30 @@ extra_compile_args = -fPIC -O2
/DGECODE_VERSION='win32' -- sys.platform == 'win32'
language = cxx
+# corner case: if the parent package of an extension is declared but
+# not its grandparent, it's legal
+[extension: parent.undeclared._speed]
+sources = parent/undeclared/_speed.c
+"""
+
+EXT_SETUP_CFG_BUGGY_1 = """
+[extension: realname]
+name = crash_here
+"""
+
+EXT_SETUP_CFG_BUGGY_2 = """
+[files]
+packages = ham
+
+[extension: spam.eggs]
+"""
+
+EXT_SETUP_CFG_BUGGY_3 = """
+[files]
+packages = ok
+ ok.works
+
+[extension: ok.works.breaks._ext]
"""
HOOKS_MODULE = """
@@ -311,7 +335,7 @@ class ConfigTestCase(support.TempdirManager,
dist = self.get_dist()
ext_modules = dict((mod.name, mod) for mod in dist.ext_modules)
- self.assertEqual(len(ext_modules), 2)
+ self.assertEqual(len(ext_modules), 3)
ext = ext_modules.get('one.speed_coconuts')
self.assertEqual(ext.sources, ['c_src/speed_coconuts.c'])
self.assertEqual(ext.define_macros, ['HAVE_CAIRO', 'HAVE_GTK2'])
@@ -335,6 +359,15 @@ class ConfigTestCase(support.TempdirManager,
self.assertEqual(ext.extra_compile_args, cargs)
self.assertEqual(ext.language, 'cxx')
+ self.write_file('setup.cfg', EXT_SETUP_CFG_BUGGY_1)
+ self.assertRaises(PackagingOptionError, self.get_dist)
+
+ self.write_file('setup.cfg', EXT_SETUP_CFG_BUGGY_2)
+ self.assertRaises(PackagingOptionError, self.get_dist)
+
+ self.write_file('setup.cfg', EXT_SETUP_CFG_BUGGY_3)
+ self.assertRaises(PackagingOptionError, self.get_dist)
+
def test_project_setup_hook_works(self):
# Bug #11637: ensure the project directory is on sys.path to allow
# project-specific hooks
@@ -358,7 +391,7 @@ class ConfigTestCase(support.TempdirManager,
self.write_setup({
'setup-hooks': '\n packaging.tests.test_config.first_hook'
'\n packaging.tests.test_config.missing_hook'
- '\n packaging.tests.test_config.third_hook'
+ '\n packaging.tests.test_config.third_hook',
})
self.write_file('README', 'yeah')
dist = self.get_dist()
diff --git a/Lib/packaging/tests/test_util.py b/Lib/packaging/tests/test_util.py
index 21ac4e2..5e804c2 100644
--- a/Lib/packaging/tests/test_util.py
+++ b/Lib/packaging/tests/test_util.py
@@ -15,7 +15,7 @@ from packaging.errors import (
from packaging import util
from packaging.dist import Distribution
from packaging.util import (
- convert_path, change_root, split_quoted, strtobool, rfc822_escape,
+ convert_path, change_root, split_quoted, strtobool,
get_compiler_versions, _MAC_OS_X_LD_VERSION, byte_compile, find_packages,
spawn, get_pypirc_path, generate_pypirc, read_pypirc, resolve_name, iglob,
RICH_GLOB, egginfo_to_distinfo, is_setuptools, is_distutils, is_packaging,
@@ -255,13 +255,6 @@ class UtilTestCase(support.EnvironRestorer,
for n in no:
self.assertFalse(strtobool(n))
- def test_rfc822_escape(self):
- header = 'I am a\npoor\nlonesome\nheader\n'
- res = rfc822_escape(header)
- wanted = ('I am a%(8s)spoor%(8s)slonesome%(8s)s'
- 'header%(8s)s') % {'8s': '\n' + 8 * ' '}
- self.assertEqual(res, wanted)
-
def test_find_exe_version(self):
# the ld version scheme under MAC OS is:
# ^@(#)PROGRAM:ld PROJECT:ld64-VERSION
diff --git a/Lib/packaging/util.py b/Lib/packaging/util.py
index f94be5d..49c5991 100644
--- a/Lib/packaging/util.py
+++ b/Lib/packaging/util.py
@@ -8,8 +8,6 @@ import errno
import shutil
import string
import hashlib
-import tarfile
-import zipfile
import posixpath
import subprocess
import sysconfig
@@ -23,6 +21,30 @@ from packaging.errors import (PackagingPlatformError, PackagingFileError,
PackagingByteCompileError, PackagingExecError,
InstallationException, PackagingInternalError)
+__all__ = [
+ # file dependencies
+ 'newer', 'newer_group',
+ # helpers for commands (dry-run system)
+ 'execute', 'write_file',
+ # spawning programs
+ 'find_executable', 'spawn',
+ # path manipulation
+ 'convert_path', 'change_root',
+ # 2to3 conversion
+ 'Mixin2to3', 'run_2to3',
+ # packaging compatibility helpers
+ 'cfg_to_args', 'generate_setup_py',
+ 'egginfo_to_distinfo',
+ 'get_install_method',
+ # misc
+ 'ask', 'check_environ', 'encode_multipart', 'resolve_name',
+ # querying for information TODO move to sysconfig
+ 'get_compiler_versions', 'get_platform', 'set_platform',
+ # configuration TODO move to packaging.config
+ 'get_pypirc_path', 'read_pypirc', 'generate_pypirc',
+ 'strtobool', 'split_multiline',
+]
+
_PLATFORM = None
_DEFAULT_INSTALLER = 'packaging'
@@ -152,31 +174,6 @@ def check_environ():
_environ_checked = True
-def subst_vars(s, local_vars):
- """Perform shell/Perl-style variable substitution on 'string'.
-
- Every occurrence of '$' followed by a name is considered a variable, and
- variable is substituted by the value found in the 'local_vars'
- dictionary, or in 'os.environ' if it's not in 'local_vars'.
- 'os.environ' is first checked/augmented to guarantee that it contains
- certain values: see 'check_environ()'. Raise ValueError for any
- variables not found in either 'local_vars' or 'os.environ'.
- """
- check_environ()
-
- def _subst(match, local_vars=local_vars):
- var_name = match.group(1)
- if var_name in local_vars:
- return str(local_vars[var_name])
- else:
- return os.environ[var_name]
-
- try:
- return re.sub(r'\$([a-zA-Z_][a-zA-Z_0-9]*)', _subst, s)
- except KeyError as var:
- raise ValueError("invalid variable '$%s'" % var)
-
-
# Needed by 'split_quoted()'
_wordchars_re = _squote_re = _dquote_re = None
@@ -188,6 +185,8 @@ def _init_regex():
_dquote_re = re.compile(r'"(?:[^"\\]|\\.)*"')
+# TODO replace with shlex.split after testing
+
def split_quoted(s):
"""Split a string up according to Unix shell-like rules for quotes and
backslashes.
@@ -435,15 +434,6 @@ byte_compile(files, optimize=%r, force=%r,
file, cfile_base)
-def rfc822_escape(header):
- """Return a form of *header* suitable for inclusion in an RFC 822-header.
-
- This function ensures there are 8 spaces after each newline.
- """
- lines = header.split('\n')
- sep = '\n' + 8 * ' '
- return sep.join(lines)
-
_RE_VERSION = re.compile('(\d+\.\d+(\.\d+)*)')
_MAC_OS_X_LD_VERSION = re.compile('^@\(#\)PROGRAM:ld '
'PROJECT:ld64-((\d+)(\.\d+)*)')
@@ -543,6 +533,10 @@ def write_file(filename, contents):
"""Create *filename* and write *contents* to it.
*contents* is a sequence of strings without line terminators.
+
+ This functions is not intended to replace the usual with open + write
+ idiom in all cases, only with Command.execute, which runs depending on
+ the dry_run argument and also logs its arguments).
"""
with open(filename, "w") as f:
for line in contents:
@@ -562,6 +556,7 @@ def _is_archive_file(name):
def _under(path, root):
+ # XXX use os.path
path = path.split(os.sep)
root = root.split(os.sep)
if len(root) > len(path):
@@ -664,103 +659,11 @@ def splitext(path):
return base, ext
-def unzip_file(filename, location, flatten=True):
- """Unzip the file *filename* into the *location* directory."""
- if not os.path.exists(location):
- os.makedirs(location)
- with open(filename, 'rb') as zipfp:
- zip = zipfile.ZipFile(zipfp)
- leading = has_leading_dir(zip.namelist()) and flatten
- for name in zip.namelist():
- data = zip.read(name)
- fn = name
- if leading:
- fn = split_leading_dir(name)[1]
- fn = os.path.join(location, fn)
- dir = os.path.dirname(fn)
- if not os.path.exists(dir):
- os.makedirs(dir)
- if fn.endswith('/') or fn.endswith('\\'):
- # A directory
- if not os.path.exists(fn):
- os.makedirs(fn)
- else:
- with open(fn, 'wb') as fp:
- fp.write(data)
-
-
-def untar_file(filename, location):
- """Untar the file *filename* into the *location* directory."""
- if not os.path.exists(location):
- os.makedirs(location)
- if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'):
- mode = 'r:gz'
- elif (filename.lower().endswith('.bz2')
- or filename.lower().endswith('.tbz')):
- mode = 'r:bz2'
- elif filename.lower().endswith('.tar'):
- mode = 'r'
- else:
- mode = 'r:*'
- with tarfile.open(filename, mode) as tar:
- leading = has_leading_dir(member.name for member in tar.getmembers())
- for member in tar.getmembers():
- fn = member.name
- if leading:
- fn = split_leading_dir(fn)[1]
- path = os.path.join(location, fn)
- if member.isdir():
- if not os.path.exists(path):
- os.makedirs(path)
- else:
- try:
- fp = tar.extractfile(member)
- except (KeyError, AttributeError):
- # Some corrupt tar files seem to produce this
- # (specifically bad symlinks)
- continue
- try:
- if not os.path.exists(os.path.dirname(path)):
- os.makedirs(os.path.dirname(path))
- with open(path, 'wb') as destfp:
- shutil.copyfileobj(fp, destfp)
- finally:
- fp.close()
-
-
-def has_leading_dir(paths):
- """Return true if all the paths have the same leading path name.
-
- In other words, check that everything is in one subdirectory in an
- archive.
- """
- common_prefix = None
- for path in paths:
- prefix, rest = split_leading_dir(path)
- if not prefix:
- return False
- elif common_prefix is None:
- common_prefix = prefix
- elif prefix != common_prefix:
- return False
- return True
-
-
-def split_leading_dir(path):
- path = str(path)
- path = path.lstrip('/').lstrip('\\')
- if '/' in path and (('\\' in path and path.find('/') < path.find('\\'))
- or '\\' not in path):
- return path.split('/', 1)
- elif '\\' in path:
- return path.split('\\', 1)
- else:
- return path, ''
-
if sys.platform == 'darwin':
_cfg_target = None
_cfg_target_split = None
+
def spawn(cmd, search_path=True, verbose=0, dry_run=False, env=None):
"""Run another program specified as a command list 'cmd' in a new process.
@@ -1510,7 +1413,7 @@ def encode_multipart(fields, files, boundary=None):
for key, values in fields:
# handle multiple entries for the same name
if not isinstance(values, (tuple, list)):
- values=[values]
+ values = [values]
for value in values:
l.extend((
diff --git a/Lib/pipes.py b/Lib/pipes.py
index 693309f..f1a16f6 100644
--- a/Lib/pipes.py
+++ b/Lib/pipes.py
@@ -54,8 +54,6 @@ for the built-in function open() or for os.popen().
To create a new template object initialized to a given one:
t2 = t.clone()
-
-For an example, see the function test() at the end of the file.
""" # '
diff --git a/Lib/shutil.py b/Lib/shutil.py
index 4b75262..0c096cb 100644
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -35,7 +35,7 @@ __all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
"register_archive_format", "unregister_archive_format",
"get_unpack_formats", "register_unpack_format",
"unregister_unpack_format", "unpack_archive",
- "ignore_patterns"]
+ "ignore_patterns", "chown"]
# disk_usage is added later, if available on the platform
class Error(EnvironmentError):
@@ -791,6 +791,7 @@ elif os.name == 'nt':
used = total - free
return _ntuple_diskusage(total, used, free)
+
def chown(path, user=None, group=None):
"""Change owner user and group of the given path.