Minor documentation changes cross-referencing NullHandler to the documentation on...
[python.git] / Lib / shutil.py
blobae2c66cef1e73a73cbfc87805b7cac8ca3a0dbc2
1 """Utility functions for copying files and directory trees.
3 XXX The functions here don't copy the resource fork or other metadata on Mac.
5 """
7 import os
8 import sys
9 import stat
10 from os.path import abspath
11 import fnmatch
13 __all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2",
14 "copytree","move","rmtree","Error"]
16 class Error(EnvironmentError):
17 pass
19 try:
20 WindowsError
21 except NameError:
22 WindowsError = None
24 def copyfileobj(fsrc, fdst, length=16*1024):
25 """copy data from file-like object fsrc to file-like object fdst"""
26 while 1:
27 buf = fsrc.read(length)
28 if not buf:
29 break
30 fdst.write(buf)
32 def _samefile(src, dst):
33 # Macintosh, Unix.
34 if hasattr(os.path,'samefile'):
35 try:
36 return os.path.samefile(src, dst)
37 except OSError:
38 return False
40 # All other platforms: check for same pathname.
41 return (os.path.normcase(os.path.abspath(src)) ==
42 os.path.normcase(os.path.abspath(dst)))
44 def copyfile(src, dst):
45 """Copy data from src to dst"""
46 if _samefile(src, dst):
47 raise Error, "`%s` and `%s` are the same file" % (src, dst)
49 fsrc = None
50 fdst = None
51 try:
52 fsrc = open(src, 'rb')
53 fdst = open(dst, 'wb')
54 copyfileobj(fsrc, fdst)
55 finally:
56 if fdst:
57 fdst.close()
58 if fsrc:
59 fsrc.close()
61 def copymode(src, dst):
62 """Copy mode bits from src to dst"""
63 if hasattr(os, 'chmod'):
64 st = os.stat(src)
65 mode = stat.S_IMODE(st.st_mode)
66 os.chmod(dst, mode)
68 def copystat(src, dst):
69 """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
70 st = os.stat(src)
71 mode = stat.S_IMODE(st.st_mode)
72 if hasattr(os, 'utime'):
73 os.utime(dst, (st.st_atime, st.st_mtime))
74 if hasattr(os, 'chmod'):
75 os.chmod(dst, mode)
76 if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
77 os.chflags(dst, st.st_flags)
80 def copy(src, dst):
81 """Copy data and mode bits ("cp src dst").
83 The destination may be a directory.
85 """
86 if os.path.isdir(dst):
87 dst = os.path.join(dst, os.path.basename(src))
88 copyfile(src, dst)
89 copymode(src, dst)
91 def copy2(src, dst):
92 """Copy data and all stat info ("cp -p src dst").
94 The destination may be a directory.
96 """
97 if os.path.isdir(dst):
98 dst = os.path.join(dst, os.path.basename(src))
99 copyfile(src, dst)
100 copystat(src, dst)
102 def ignore_patterns(*patterns):
103 """Function that can be used as copytree() ignore parameter.
105 Patterns is a sequence of glob-style patterns
106 that are used to exclude files"""
107 def _ignore_patterns(path, names):
108 ignored_names = []
109 for pattern in patterns:
110 ignored_names.extend(fnmatch.filter(names, pattern))
111 return set(ignored_names)
112 return _ignore_patterns
114 def copytree(src, dst, symlinks=False, ignore=None):
115 """Recursively copy a directory tree using copy2().
117 The destination directory must not already exist.
118 If exception(s) occur, an Error is raised with a list of reasons.
120 If the optional symlinks flag is true, symbolic links in the
121 source tree result in symbolic links in the destination tree; if
122 it is false, the contents of the files pointed to by symbolic
123 links are copied.
125 The optional ignore argument is a callable. If given, it
126 is called with the `src` parameter, which is the directory
127 being visited by copytree(), and `names` which is the list of
128 `src` contents, as returned by os.listdir():
130 callable(src, names) -> ignored_names
132 Since copytree() is called recursively, the callable will be
133 called once for each directory that is copied. It returns a
134 list of names relative to the `src` directory that should
135 not be copied.
137 XXX Consider this example code rather than the ultimate tool.
140 names = os.listdir(src)
141 if ignore is not None:
142 ignored_names = ignore(src, names)
143 else:
144 ignored_names = set()
146 os.makedirs(dst)
147 errors = []
148 for name in names:
149 if name in ignored_names:
150 continue
151 srcname = os.path.join(src, name)
152 dstname = os.path.join(dst, name)
153 try:
154 if symlinks and os.path.islink(srcname):
155 linkto = os.readlink(srcname)
156 os.symlink(linkto, dstname)
157 elif os.path.isdir(srcname):
158 copytree(srcname, dstname, symlinks, ignore)
159 else:
160 copy2(srcname, dstname)
161 # XXX What about devices, sockets etc.?
162 except (IOError, os.error), why:
163 errors.append((srcname, dstname, str(why)))
164 # catch the Error from the recursive copytree so that we can
165 # continue with other files
166 except Error, err:
167 errors.extend(err.args[0])
168 try:
169 copystat(src, dst)
170 except OSError, why:
171 if WindowsError is not None and isinstance(why, WindowsError):
172 # Copying file access times may fail on Windows
173 pass
174 else:
175 errors.extend((src, dst, str(why)))
176 if errors:
177 raise Error, errors
179 def rmtree(path, ignore_errors=False, onerror=None):
180 """Recursively delete a directory tree.
182 If ignore_errors is set, errors are ignored; otherwise, if onerror
183 is set, it is called to handle the error with arguments (func,
184 path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
185 path is the argument to that function that caused it to fail; and
186 exc_info is a tuple returned by sys.exc_info(). If ignore_errors
187 is false and onerror is None, an exception is raised.
190 if ignore_errors:
191 def onerror(*args):
192 pass
193 elif onerror is None:
194 def onerror(*args):
195 raise
196 try:
197 if os.path.islink(path):
198 # symlinks to directories are forbidden, see bug #1669
199 raise OSError("Cannot call rmtree on a symbolic link")
200 except OSError:
201 onerror(os.path.islink, path, sys.exc_info())
202 # can't continue even if onerror hook returns
203 return
204 names = []
205 try:
206 names = os.listdir(path)
207 except os.error, err:
208 onerror(os.listdir, path, sys.exc_info())
209 for name in names:
210 fullname = os.path.join(path, name)
211 try:
212 mode = os.lstat(fullname).st_mode
213 except os.error:
214 mode = 0
215 if stat.S_ISDIR(mode):
216 rmtree(fullname, ignore_errors, onerror)
217 else:
218 try:
219 os.remove(fullname)
220 except os.error, err:
221 onerror(os.remove, fullname, sys.exc_info())
222 try:
223 os.rmdir(path)
224 except os.error:
225 onerror(os.rmdir, path, sys.exc_info())
228 def _basename(path):
229 # A basename() variant which first strips the trailing slash, if present.
230 # Thus we always get the last component of the path, even for directories.
231 return os.path.basename(path.rstrip(os.path.sep))
233 def move(src, dst):
234 """Recursively move a file or directory to another location. This is
235 similar to the Unix "mv" command.
237 If the destination is a directory or a symlink to a directory, the source
238 is moved inside the directory. The destination path must not already
239 exist.
241 If the destination already exists but is not a directory, it may be
242 overwritten depending on os.rename() semantics.
244 If the destination is on our current filesystem, then rename() is used.
245 Otherwise, src is copied to the destination and then removed.
246 A lot more could be done here... A look at a mv.c shows a lot of
247 the issues this implementation glosses over.
250 real_dst = dst
251 if os.path.isdir(dst):
252 real_dst = os.path.join(dst, _basename(src))
253 if os.path.exists(real_dst):
254 raise Error, "Destination path '%s' already exists" % real_dst
255 try:
256 os.rename(src, real_dst)
257 except OSError:
258 if os.path.isdir(src):
259 if destinsrc(src, dst):
260 raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst)
261 copytree(src, real_dst, symlinks=True)
262 rmtree(src)
263 else:
264 copy2(src, real_dst)
265 os.unlink(src)
267 def destinsrc(src, dst):
268 return abspath(dst).startswith(abspath(src))