HWRF  trunk@4391
cd.py
1 """!Change directory, handle temporary directories
2 
3 This module provides a means by which to change to a different
4 directory in a Python "with" block and change back out afterwards,
5 regardless of what happens inside the block. It can, optionally,
6 create a new directory, and optionally delete it at the end of the
7 block. There are two classes:
8 
9 * TempDir - creates a temporary directory with a randomly-generated
10  name, chdirs to the directory, and chdirs back out afterwards.
11  It can be configured to delete the directory afterwards (the
12  default) or not.
13 
14 * NamedDir - a subclass of TempDir that uses a specific directory
15  rather than a randomly-generated one. By default, the directory
16  is NOT deleted at the end of the block. That can be configured."""
17 
18 import tempfile, os, re, logging, sys, shutil, errno, stat
19 import produtil.listing
20 
21 ##@var __all__
22 # List of symbols to export by "from produtil.cd import *"
23 __all__=['TempDir','NamedDir','perm_remove','perm_add']
24 
25 ##@var perm_add
26 # Default permissions to add to new directories created by TempDir:
27 # user has all possible access. Group and other can read and execute.
28 # @private
29 perm_add = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | \
30  stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH
31 
32 ##@var perm_remove
33 # Permissions to remove from all directories: world write and setuid.
34 # This overrides perm_add.
35 # @private
36 perm_remove = stat.S_IWOTH|stat.S_ISUID
37 
38 class TempDir(object):
39  """!This class is intended to be used with the Python "with TempDir() as t" syntax.
40  Example:
41  @code
42  with TempDir() as t:
43  # we're now in the temporary directory
44  ...do things...
45  # the temporary directory has been deleted now
46  @endcode
47  """
48  def __init__(self,suffix='.tmp',prefix='tempdir.',dir=None,keep=False,
49  logger=None,print_on_exception=True,add_perms=perm_add,
50  remove_perms=perm_remove,keep_on_error=True,cd=True):
51  """!Creates a TempDir.
52 
53  @param suffix,prefix,dir Passed to the tempfile.mkdtemp to
54  generate the directory name. Meanings are the same as in
55  that constructor. See the Python documentation for
56  details.
57  @param keep Controls directory deletion if the "with" block
58  returns without an exception. If False, the directory is
59  deleted. Default: keep=False
60  @param logger A logging.logger for log messages
61  @param print_on_exception Print exceptions before leaving the dir
62  @param add_perms Permissions to add to the directory. Default: 755.
63  @param remove_perms Permissions to remove from the directory.
64  Default: setuid and world write.
65  @param keep_on_error Controls directory deletion if the "with"
66  block raises an Exception or GeneratorExit, or subclass
67  thereof. If False, the directory is deleted under those
68  circumstances. Default: keep_on_error=True.
69  @param cd If True (default), cd to the directory in the "with"
70  block and cd back out afterwards. If False, then only
71  directory creation and deletion happens. """
72  self.dirname=None
73  self.suffix=suffix
74  self.prefix=prefix
75  self.print_on_exception=print_on_exception
76  if dir is None:
77  dir='.'
78  self.dir=dir
79  self.olddir=None
80  self._keep=keep
81  self._logger=logger
82  self._add_perms=int(add_perms)
83  self._remove_perms=int(remove_perms)
84  self._keep_on_error=keep_on_error
85  self._cd=bool(cd)
86  assert(dir is not None)
87  if not os.path.isabs(self.dir):
88  self.dir=os.path.join(os.getcwd(),self.dir)
89  assert(os.path.isabs(self.dir))
90  ##@var dirname
91  # The name of the target directory.
92 
93  ##@var olddir
94  # The name of the directory we came from.
95 
96  ##@var suffix
97  # Temporary directory name suffix
98 
99  ##@var prefix
100  # Temporary directory name prefix
101 
102  ##@var print_on_exception
103  # Should we print exceptions before exiting the directory?
104 
105  ##@var dir
106  # The directory object.
107 
108  def name_make_dir(self):
109  """!Decide the name of the directory, and create the directory.
110  Also create any path components leading up to the
111  directory."""
112  if self.prefix is not None:
113  try:
114  d=os.path.dirname(self.prefix)
115  if d is not None and d!='':
116  os.makedirs(d)
117  except EnvironmentError as e:
118  if e.errno != errno.EEXIST:
119  raise
120  self.dirname=tempfile.mkdtemp(suffix=self.suffix,prefix=self.prefix,
121  dir=self.dir)
122  # Add requested permissions to the directory. Remove world write.
123  s=os.stat(self.dirname)
124  os.chmod(self.dirname, (s.st_mode | int(self._add_perms))
125  & ~self._remove_perms )
126  def mkdir_cd(self):
127  """!Creates the temporary directory and chdirs the current
128  process into that directory. It calls self.name_make_dir() to
129  do the naming and directory creation."""
130  self.olddir=os.getcwd()
131  self.name_make_dir()
132  if self._cd: os.chdir(self.dirname)
133  if self._logger is not None:
134  self._logger.info('chdir to temporary directory '+self.dirname)
135  def cd_out(self):
136  """!Exit the temporary directory created by mkdir_cd and
137  return to the original directory, if possible."""
138  if self._logger is not None:
139  self._logger.info('chdir to old directory '+self.olddir)
140  if self._cd: os.chdir(self.olddir)
141  def cd_rmdir(self):
142  """!CD out and remove the old directory.
143 
144  This subroutine exits the temporary directory created by
145  mkdir_cd, and then deletes that temporary directory. After
146  this routine, the process will be in its original directory
147  (from before the call to mkdir_cd) if possible, or otherwise
148  it will be in the root directory (/).
149 
150  It is the caller's responsibility to ensure this function is
151  not called if keep_on_error=True and an error occurs."""
152  try:
153  if self._cd: self.cd_out()
154  except(Exception) as e:
155  if self._logger is not None:
156  self._logger.critical('could not chdir, so chdir to root '
157  'because of exception: '+repr(e))
158  if self._cd: os.chdir('/')
159  finally:
160  if self.dirname is not None and not self._keep \
161  and os.path.isabs(self.dirname):
162  if self._logger is not None:
163  self._logger.info('%s: delete temporary directory'
164  %(self.dirname,))
165  if self._logger is not None:
166  shutil.rmtree(self.dirname,onerror=self._rmerror)
167  else:
168  shutil.rmtree(self.dirname,True)
169  if self._logger is not None and os.path.exists(self.dirname):
170  self._logger.warning('%s: could not delete directory'
171  %(self.dirname,))
172  elif self.dirname is not None and self._logger is not None:
173  self._logger.info('%s: not deleting temporary directory'
174  %(self.dirname,))
175  return False
176  def _rmerror(self,function,path,excinfo):
177  """!Called when a file removal error happens.
178  @param function,path,excinfo exception information"""
179  if self._logger is not None:
180  self._logger('%s: cannot remove'%(str(path),),exc_info=excinfo)
181  def exception_info(self):
182  """!Called to dump information to a log, or failing that, the
183  terminal if an unexpected exception is caught."""
184  if self._logger is not None:
185  self._logger.warning(self.dirname
186  +': leaving temp directory due to exception')
187  self._logger.info('%s: listing current directory (%s) via ls -l',
188  self.dirname,os.getcwd())
189  else:
190  logging.getLogger('produtil.tempdir').warning(
191  self.dirname+': leaving temp directory due to exception')
192  print produtil.listing.Listing('.')
193  def __enter__(self):
194  """!This is a simple wrapper around mkdir_cd that is intended
195  to be used with in a "with" block. This subroutine is
196  automatically called at the beginning of the block."""
197  self.mkdir_cd()
198  return self
199  def __exit__(self,etype,value,traceback):
200  """!Exit the 'with' block.
201 
202  This is a simple wrapper around cd_rmdir that is intended to
203  be used with in a "with" block. This subroutine is
204  automatically called at the end of the block. It will call
205  cd_rmdir to delete the directory unless an exception is thrown
206  that is NOT a subclass of Exception or GeneratorExit. The
207  removal is skipped to allow the program to exit quickly in
208  case of a fatal signal (ie.: SIGQUIT, SIGTERM, SIGINT,
209  SIGHUP).
210  @param etype,value,traceback exception information"""
211  if value is None or isinstance(value,GeneratorExit):
212  # Normal exit
213  self.cd_rmdir() # will not delete if self.keep
214  elif isinstance(value,Exception):
215  # Exception thrown, but not a fatal exception and not a
216  # break statement (GeneratorExit)
217  if isinstance(value,Exception) and self.print_on_exception:
218  self.exception_info()
219  if self._keep_on_error:
220  self.cd_out()
221  else:
222  self.cd_rmdir()
223  else:
224  # Fatal error such as KeyboardInterrupt or SystemExit. Do
225  # as little as is safe:
226  self.cd_out() # no rmdir
227 
229  """!This subclass of TempDir takes a directory name, instead of
230  generating one automatically. By default, it will NOT delete the
231  directory upon __exit__. That can be overridden by specifying
232  keep=False."""
233  def __init__(self,dirname,keep=True,logger=None,keep_on_error=True,
234  add_perms=0,remove_perms=0,rm_first=False):
235  """!Create a NamedDir for the specified directory. The given
236  logger is used to log messages. There are two deletion
237  vs. non-deletion options:
238 
239  @param dirname The directory name
240  @param keep If False, the file is deleted upon successful return
241  of the "with" block. If True, the file is kept upon
242  successful return.
243  @param logger A logging.logger for log messages
244  @param add_perms Permissions to add to the directory after cding
245  into it. Default: none.
246  @param remove_perms Permissions to remove from the directory after
247  cding into it. Default: none.
248  @param keep_on_error Controls deletion upon catching of an
249  Exception or GeneratorException (or subclass thereof).
250  @param rm_first If the directory already exists, delete it first
251  and make a new one before cding to it."""
252  if not isinstance(dirname,basestring):
253  raise TypeError(
254  'NamedDir requires a string name as its first argument.')
255  super(NamedDir,self).__init__(
256  keep=keep,logger=logger,keep_on_error=keep_on_error,
257  add_perms=add_perms,remove_perms=remove_perms)
258  self.dirname=dirname
259  self._rm_first=bool(rm_first)
260  ##@var dirname
261  # The directory name specified in the constructor.
262  def name_make_dir(self):
263  """!Replacement for the TempDir.name_make_dir. Uses the
264  directory name specified in the constructor."""
265  if not self._cd: return
266  if self._rm_first:
267  if os.path.exists(self.dirname):
268  if self._logger is not None:
269  shutil.rmtree(self.dirname,onerror=self._rmerror)
270  else:
271  shutil.rmtree(self.dirname,True)
272  if not os.path.exists(self.dirname):
273  os.makedirs(self.dirname)
274  # If requested, modify the directory permissions:
275  if self._add_perms!=0 or self._remove_perms!=0:
276  s=os.stat(self.dirname)
277  os.chmod(self.dirname, (s.st_mode | int(self._add_perms))
278  & ~self._remove_perms )
Imitates the shell "ls -l" program.
Definition: listing.py:9
def __exit__(self, etype, value, traceback)
Exit the 'with' block.
Definition: cd.py:199
print_on_exception
Should we print exceptions before exiting the directory?
Definition: cd.py:75
def cd_out(self)
Exit the temporary directory created by mkdir_cd and return to the original directory, if possible.
Definition: cd.py:135
prefix
Temporary directory name prefix.
Definition: cd.py:74
def cd_rmdir(self)
CD out and remove the old directory.
Definition: cd.py:141
olddir
The name of the directory we came from.
Definition: cd.py:79
def _rmerror(self, function, path, excinfo)
Called when a file removal error happens.
Definition: cd.py:176
dir
The directory object.
Definition: cd.py:78
This subclass of TempDir takes a directory name, instead of generating one automatically.
Definition: cd.py:228
def __init__
Create a NamedDir for the specified directory.
Definition: cd.py:234
suffix
Temporary directory name suffix.
Definition: cd.py:73
This class is intended to be used with the Python "with TempDir() as t" syntax.
Definition: cd.py:38
def name_make_dir(self)
Decide the name of the directory, and create the directory.
Definition: cd.py:108
Contains the Listing class, which emulates "ls -l".
Definition: listing.py:1
def __init__
Creates a TempDir.
Definition: cd.py:50
def __enter__(self)
This is a simple wrapper around mkdir_cd that is intended to be used with in a "with" block...
Definition: cd.py:193
def exception_info(self)
Called to dump information to a log, or failing that, the terminal if an unexpected exception is caug...
Definition: cd.py:181
def name_make_dir(self)
Replacement for the TempDir.name_make_dir.
Definition: cd.py:262
dirname
The name of the target directory.
Definition: cd.py:72
def mkdir_cd(self)
Creates the temporary directory and chdirs the current process into that directory.
Definition: cd.py:126