[Python-ideas] chdir context manager
Chris Jerdonek
chris.jerdonek at gmail.com
Sat Jan 19 19:52:54 CET 2013
On Sat, Jan 19, 2013 at 2:10 AM, Daniel Shahaf <d.s at daniel.shahaf.name> wrote:
> The following is a common pattern (used by, for example,
> shutil.make_archive):
>
> save_cwd = os.getcwd()
> try:
> foo()
> finally:
> os.chdir(save_cwd)
FWIW, test.support has such a context manager (though test.support is
not for public consumption and test.support's implementation does more
than one thing, though see issue 15415):
http://hg.python.org/cpython/file/48cddcb9c841/Lib/test/support.py#l738
--Chris
>
> I suggest this deserves a context manager:
>
> with saved_cwd():
> foo()
>
> Initial feedback on IRC suggests shutil as where this functionality
> should live (other suggestions were made, such as pathlib). Hence,
> attached patch implements this as shutil.saved_cwd, based on os.fchdir.
>
> The patch also adds os.chdir to os.supports_dir_fd and documents the
> context manager abilities of builtins.open() in its reference.
>
> Thoughts?
>
> Thanks,
>
> Daniel
>
>
> diff -r 74b0461346f0 Doc/library/functions.rst
> --- a/Doc/library/functions.rst Fri Jan 18 17:53:18 2013 -0800
> +++ b/Doc/library/functions.rst Sat Jan 19 09:39:27 2013 +0000
> @@ -828,6 +828,9 @@ are always available. They are listed h
> Open *file* and return a corresponding :term:`file object`. If the file
> cannot be opened, an :exc:`OSError` is raised.
>
> + This function can be used as a :term:`context manager` that closes the
> + file when it exits.
> +
> *file* is either a string or bytes object giving the pathname (absolute or
> relative to the current working directory) of the file to be opened or
> an integer file descriptor of the file to be wrapped. (If a file descriptor
> diff -r 74b0461346f0 Doc/library/os.rst
> --- a/Doc/library/os.rst Fri Jan 18 17:53:18 2013 -0800
> +++ b/Doc/library/os.rst Sat Jan 19 09:39:27 2013 +0000
> @@ -1315,6 +1315,9 @@ features:
> This function can support :ref:`specifying a file descriptor <path_fd>`. The
> descriptor must refer to an opened directory, not an open file.
>
> + See also :func:`shutil.saved_cwd` for a context manager that restores the
> + current working directory.
> +
> Availability: Unix, Windows.
>
> .. versionadded:: 3.3
> diff -r 74b0461346f0 Doc/library/shutil.rst
> --- a/Doc/library/shutil.rst Fri Jan 18 17:53:18 2013 -0800
> +++ b/Doc/library/shutil.rst Sat Jan 19 09:39:27 2013 +0000
> @@ -36,6 +36,19 @@ copying and removal. For operations on i
> Directory and files operations
> ------------------------------
>
> +.. function:: saved_cwd()
> +
> + Return a :term:`context manager` that restores the current working directory
> + when it exits. See :func:`os.chdir` for changing the current working
> + directory.
> +
> + The context manager returns an open file descriptor for the saved directory.
> +
> + Only available when :func:`os.chdir` supports file descriptor arguments.
> +
> + .. versionadded:: 3.4
> +
> +
> .. function:: copyfileobj(fsrc, fdst[, length])
>
> Copy the contents of the file-like object *fsrc* to the file-like object *fdst*.
> diff -r 74b0461346f0 Lib/os.py
> --- a/Lib/os.py Fri Jan 18 17:53:18 2013 -0800
> +++ b/Lib/os.py Sat Jan 19 09:39:27 2013 +0000
> @@ -120,6 +120,7 @@ if _exists("_have_functions"):
>
> _set = set()
> _add("HAVE_FACCESSAT", "access")
> + _add("HAVE_FCHDIR", "chdir")
> _add("HAVE_FCHMODAT", "chmod")
> _add("HAVE_FCHOWNAT", "chown")
> _add("HAVE_FSTATAT", "stat")
> diff -r 74b0461346f0 Lib/shutil.py
> --- a/Lib/shutil.py Fri Jan 18 17:53:18 2013 -0800
> +++ b/Lib/shutil.py Sat Jan 19 09:39:27 2013 +0000
> @@ -38,6 +38,7 @@ __all__ = ["copyfileobj", "copyfile", "c
> "unregister_unpack_format", "unpack_archive",
> "ignore_patterns", "chown", "which"]
> # disk_usage is added later, if available on the platform
> + # saved_cwd is added later, if available on the platform
>
> class Error(OSError):
> pass
> @@ -1111,3 +1112,20 @@ def which(cmd, mode=os.F_OK | os.X_OK, p
> if _access_check(name, mode):
> return name
> return None
> +
> +# Define the chdir context manager.
> +if os.chdir in os.supports_dir_fd:
> + class saved_cwd:
> + def __init__(self):
> + pass
> + def __enter__(self):
> + self.dh = os.open(os.curdir,
> + os.O_RDONLY | getattr(os, 'O_DIRECTORY', 0))
> + return self.dh
> + def __exit__(self, exc_type, exc_value, traceback):
> + try:
> + os.chdir(self.dh)
> + finally:
> + os.close(self.dh)
> + return False
> + __all__.append('saved_cwd')
> diff -r 74b0461346f0 Lib/test/test_shutil.py
> --- a/Lib/test/test_shutil.py Fri Jan 18 17:53:18 2013 -0800
> +++ b/Lib/test/test_shutil.py Sat Jan 19 09:39:27 2013 +0000
> @@ -1276,6 +1276,20 @@ class TestShutil(unittest.TestCase):
> rv = shutil.copytree(src_dir, dst_dir)
> self.assertEqual(['foo'], os.listdir(rv))
>
> + def test_saved_cwd(self):
> + if hasattr(os, 'fchdir'):
> + temp_dir = self.mkdtemp()
> + orig_dir = os.getcwd()
> + with shutil.saved_cwd() as dir_fd:
> + os.chdir(temp_dir)
> + new_dir = os.getcwd()
> + self.assertIsInstance(dir_fd, int)
> + final_dir = os.getcwd()
> + self.assertEqual(orig_dir, final_dir)
> + self.assertEqual(temp_dir, new_dir)
> + else:
> + self.assertFalse(hasattr(shutil, 'saved_cwd'))
> +
>
> class TestWhich(unittest.TestCase):
>
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> http://mail.python.org/mailman/listinfo/python-ideas
More information about the Python-ideas
mailing list