diff --git a/newsfragments/+reentrant-context.bugfix.rst b/newsfragments/+reentrant-context.bugfix.rst new file mode 100644 index 0000000..1e46329 --- /dev/null +++ b/newsfragments/+reentrant-context.bugfix.rst @@ -0,0 +1 @@ +Nested use of the same ``Path`` as a context manager now restores the original working directory. diff --git a/path/__init__.py b/path/__init__.py index 33dd979..a8d33bd 100644 --- a/path/__init__.py +++ b/path/__init__.py @@ -243,12 +243,14 @@ def __rtruediv__(self, rel: str) -> Self: return self._next_class(self.module.join(rel, self)) def __enter__(self) -> Self: - self._old_dir = self.cwd() + old_dir = self.cwd() os.chdir(self) + self._old_dirs = getattr(self, '_old_dirs', []) + self._old_dirs.append(old_dir) return self def __exit__(self, *_) -> None: - os.chdir(self._old_dir) + os.chdir(self._old_dirs.pop()) @classmethod def cwd(cls): diff --git a/tests/test_path.py b/tests/test_path.py index b424bb6..fe52edf 100644 --- a/tests/test_path.py +++ b/tests/test_path.py @@ -471,6 +471,22 @@ def test_context_manager(self, tmpdir): assert os.getcwd() == os.path.realpath(subdir) assert os.getcwd() == old_dir + @pytest.mark.parametrize('raise_inside', [False, True]) + def test_reentrant_context_manager(self, tmpdir, raise_inside): + directory = Path(tmpdir) + old_dir = os.getcwd() + try: + with directory: + with contextlib.suppress(ValueError): + with directory: + assert Path.cwd() == directory.realpath() + if raise_inside: + raise ValueError('inner context') + assert Path.cwd() == directory.realpath() + assert os.getcwd() == old_dir + finally: + os.chdir(old_dir) + def test_touch(self, tmpdir): # NOTE: This test takes a long time to run (~10 seconds). # It sleeps several seconds because on Windows, the resolution