A context manager that handle a temporary change of the working directory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | #!/usr/bin/python
# -*- encoding: utf-8 -*-
from __future__ import with_statement
import os
import os.path
class ChangeDirectory(object):
"""
ChangeDirectory is a context manager that allowing
you to temporary change the working directory.
>>> import tempfile
>>> td = os.path.realpath(tempfile.mkdtemp())
>>> currentdirectory = os.getcwd()
>>> with ChangeDirectory(td) as cd:
... assert cd.current == td
... assert os.getcwd() == td
... assert cd.previous == currentdirectory
... assert os.path.normpath(os.path.join(cd.current, cd.relative)) == cd.previous
...
>>> assert os.getcwd() == currentdirectory
>>> with ChangeDirectory(td) as cd:
... os.mkdir('foo')
... with ChangeDirectory('foo') as cd2:
... assert cd2.previous == cd.current
... assert cd2.relative == '..'
... assert os.getcwd() == os.path.join(td, 'foo')
... assert os.getcwd() == td
... assert cd.current == td
... os.rmdir('foo')
...
>>> os.rmdir(td)
>>> with ChangeDirectory('.') as cd:
... assert cd.current == currentdirectory
... assert cd.current == cd.previous
... assert cd.relative == '.'
"""
def __init__(self, directory):
self._dir = directory
self._cwd = os.getcwd()
self._pwd = self._cwd
@property
def current(self):
return self._cwd
@property
def previous(self):
return self._pwd
@property
def relative(self):
c = self._cwd.split(os.path.sep)
p = self._pwd.split(os.path.sep)
l = min(len(c), len(p))
i = 0
while i < l and c[i] == p[i]:
i += 1
return os.path.normpath(os.path.join(*(['.'] + (['..'] * (len(c) - i)) + p[i:])))
def __enter__(self):
self._pwd = self._cwd
os.chdir(self._dir)
self._cwd = os.getcwd()
return self
def __exit__(self, *args):
os.chdir(self._pwd)
self._cwd = self._pwd
if __name__ == '__main__':
import doctest
doctest.testmod()
|
might be easier to use contextlib.contexmanager decorator, if that exists in your python version.
Nice! I'm using something a bit simpler, since all the information from the properties of
ChangeDirectory
is available outside the context mnager:I added the try/finally lines so that if an exception occurs, then the directory is still switched back.
@Greg: Adding the try/finally has no effect; even without them, the directory is switched back if an exception occurs. That is the whole point of a context manager!
The code by Carlos is the correct solution.
@Greg: Woah, sorry, you were right. It turns out you do need the try/finally block.
I thought there was no way that yield could possibly throw an exception because the code called as a result of a yield happens outside the generator's call stack, but it turns out that it is possible by calling the .throw() method of a generator. contextlib does this for contextmanager-decorated generators, so an exception in a with block propogates to an exception in the decorated generator.