.. note::
This function is not thread-safe when custom archivers registered
- with :func:`register_archive_format` are used. In this case it
+ with :func:`register_archive_format` do not support the *root_dir*
+ argument. In this case it
temporarily changes the current working directory of the process
- to perform archiving.
+ to *root_dir* to perform archiving.
.. versionchanged:: 3.8
The modern pax (POSIX.1-2001) format is now used instead of
Further arguments are passed as keyword arguments: *owner*, *group*,
*dry_run* and *logger* (as passed in :func:`make_archive`).
+ If *function* has the custom attribute ``function.supports_root_dir`` set to ``True``,
+ the *root_dir* argument is passed as a keyword argument.
+ Otherwise the current working directory of the process is temporarily
+ changed to *root_dir* before calling *function*.
+ In this case :func:`make_archive` is not thread-safe.
+
If given, *extra_args* is a sequence of ``(name, value)`` pairs that will be
used as extra keywords arguments when the archiver callable is used.
*description* is used by :func:`get_archive_formats` which returns the
list of archivers. Defaults to an empty string.
+ .. versionchanged:: 3.12
+ Added support for functions supporting the *root_dir* argument.
+
.. function:: unregister_archive_format(name)
for a process with :func:`os.pidfd_open` in non-blocking mode.
(Contributed by Kumar Aditya in :gh:`93312`.)
+shutil
+------
+
+* :func:`shutil.make_archive` now passes the *root_dir* argument to custom
+ archivers which support it.
+ In this case it no longer temporarily changes the current working directory
+ of the process to *root_dir* to perform archiving.
+ (Contributed by Serhiy Storchaka in :gh:`74696`.)
+
sqlite3
-------
zip_filename = os.path.abspath(zip_filename)
return zip_filename
+_make_tarball.supports_root_dir = True
+_make_zipfile.supports_root_dir = True
+
# Maps the name of the archive format to a tuple containing:
# * the archiving function
# * extra keyword arguments
# * description
-# * does it support the root_dir argument?
_ARCHIVE_FORMATS = {
'tar': (_make_tarball, [('compress', None)],
- "uncompressed tar file", True),
+ "uncompressed tar file"),
}
if _ZLIB_SUPPORTED:
_ARCHIVE_FORMATS['gztar'] = (_make_tarball, [('compress', 'gzip')],
- "gzip'ed tar-file", True)
- _ARCHIVE_FORMATS['zip'] = (_make_zipfile, [], "ZIP file", True)
+ "gzip'ed tar-file")
+ _ARCHIVE_FORMATS['zip'] = (_make_zipfile, [], "ZIP file")
if _BZ2_SUPPORTED:
_ARCHIVE_FORMATS['bztar'] = (_make_tarball, [('compress', 'bzip2')],
- "bzip2'ed tar-file", True)
+ "bzip2'ed tar-file")
if _LZMA_SUPPORTED:
_ARCHIVE_FORMATS['xztar'] = (_make_tarball, [('compress', 'xz')],
- "xz'ed tar-file", True)
+ "xz'ed tar-file")
def get_archive_formats():
"""Returns a list of supported formats for archiving and unarchiving.
if not isinstance(element, (tuple, list)) or len(element) !=2:
raise TypeError('extra_args elements are : (arg_name, value)')
- _ARCHIVE_FORMATS[name] = (function, extra_args, description, False)
+ _ARCHIVE_FORMATS[name] = (function, extra_args, description)
def unregister_archive_format(name):
del _ARCHIVE_FORMATS[name]
if base_dir is None:
base_dir = os.curdir
- support_root_dir = format_info[3]
+ supports_root_dir = getattr(func, 'supports_root_dir', False)
save_cwd = None
if root_dir is not None:
- if support_root_dir:
+ if supports_root_dir:
# Support path-like base_name here for backwards-compatibility.
base_name = os.fspath(base_name)
kwargs['root_dir'] = root_dir
finally:
archive.close()
+ def test_make_archive_cwd_default(self):
+ current_dir = os.getcwd()
+ def archiver(base_name, base_dir, **kw):
+ self.assertNotIn('root_dir', kw)
+ self.assertEqual(base_name, 'basename')
+ self.assertEqual(os.getcwd(), current_dir)
+ raise RuntimeError()
+
+ register_archive_format('xxx', archiver, [], 'xxx file')
+ try:
+ with no_chdir:
+ with self.assertRaises(RuntimeError):
+ make_archive('basename', 'xxx')
+ self.assertEqual(os.getcwd(), current_dir)
+ finally:
+ unregister_archive_format('xxx')
+
def test_make_archive_cwd(self):
current_dir = os.getcwd()
root_dir = self.mkdtemp()
- def _breaks(*args, **kw):
+ def archiver(base_name, base_dir, **kw):
+ self.assertNotIn('root_dir', kw)
+ self.assertEqual(base_name, os.path.join(current_dir, 'basename'))
+ self.assertEqual(os.getcwd(), root_dir)
raise RuntimeError()
dirs = []
def _chdir(path):
dirs.append(path)
orig_chdir(path)
- register_archive_format('xxx', _breaks, [], 'xxx file')
+ register_archive_format('xxx', archiver, [], 'xxx file')
try:
with support.swap_attr(os, 'chdir', _chdir) as orig_chdir:
- try:
- make_archive('xxx', 'xxx', root_dir=root_dir)
- except Exception:
- pass
+ with self.assertRaises(RuntimeError):
+ make_archive('basename', 'xxx', root_dir=root_dir)
self.assertEqual(os.getcwd(), current_dir)
self.assertEqual(dirs, [root_dir, current_dir])
finally:
unregister_archive_format('xxx')
+ def test_make_archive_cwd_supports_root_dir(self):
+ current_dir = os.getcwd()
+ root_dir = self.mkdtemp()
+ def archiver(base_name, base_dir, **kw):
+ self.assertEqual(base_name, 'basename')
+ self.assertEqual(kw['root_dir'], root_dir)
+ self.assertEqual(os.getcwd(), current_dir)
+ raise RuntimeError()
+ archiver.supports_root_dir = True
+
+ register_archive_format('xxx', archiver, [], 'xxx file')
+ try:
+ with no_chdir:
+ with self.assertRaises(RuntimeError):
+ make_archive('basename', 'xxx', root_dir=root_dir)
+ self.assertEqual(os.getcwd(), current_dir)
+ finally:
+ unregister_archive_format('xxx')
+
def test_make_tarfile_in_curdir(self):
# Issue #21280
root_dir = self.mkdtemp()
--- /dev/null
+:func:`shutil.make_archive` now passes the *root_dir* argument to custom
+archivers which support it.