@@ -95,25 +95,45 @@ def osgroup():
9595
9696
9797def copytree (src , dst , symlinks = False , ignore = None , copy_function = shutil .copy2 ,
98- ignore_dangling_symlinks = False ):
99- '''Same as shutil.copytree() but valid also if 'dst' exists.
100-
101- In this case it will first remove it and then call the standard
102- shutil.copytree().'''
98+ ignore_dangling_symlinks = False , dirs_exist_ok = False ):
99+ '''Compatibility version of :py:func:`shutil.copytree()` for Python <= 3.8
100+ '''
103101 if src == os .path .commonpath ([src , dst ]):
104102 raise ValueError ("cannot copy recursively the parent directory "
105103 "`%s' into one of its descendants `%s'" % (src , dst ))
106104
107- if os .path .exists (dst ):
108- shutil .rmtree (dst )
105+ if sys .version_info [1 ] >= 8 :
106+ return shutil .copytree (src , dst , symlinks , ignore , copy_function ,
107+ ignore_dangling_symlinks , dirs_exist_ok )
108+
109+ if not dirs_exist_ok :
110+ return shutil .copytree (src , dst , symlinks , ignore , copy_function ,
111+ ignore_dangling_symlinks )
112+
113+ # dirs_exist_ok=True and Python < 3.8
114+ if not os .path .exists (dst ):
115+ return shutil .copytree (src , dst , symlinks , ignore , copy_function ,
116+ ignore_dangling_symlinks )
117+
118+ # dst exists; manually descend into the subdirectories
119+ _ , subdirs , files = list (os .walk (src ))[0 ]
120+ ignore_paths = ignore (src , os .listdir (src )) if ignore else {}
121+ for f in files :
122+ if f not in ignore_paths :
123+ copy_function (os .path .join (src , f ), os .path .join (dst , f ))
109124
110- shutil .copytree (src , dst , symlinks , ignore , copy_function ,
111- ignore_dangling_symlinks )
125+ for d in subdirs :
126+ if d not in ignore_paths :
127+ copytree (os .path .join (src , d ), os .path .join (dst , d ),
128+ symlinks , ignore , copy_function ,
129+ ignore_dangling_symlinks , dirs_exist_ok )
130+
131+ return dst
112132
113133
114134def copytree_virtual (src , dst , file_links = [],
115135 symlinks = False , copy_function = shutil .copy2 ,
116- ignore_dangling_symlinks = False ):
136+ ignore_dangling_symlinks = False , dirs_exist_ok = False ):
117137 '''Copy `dst` to `src`, but create symlinks for the files in `file_links`.
118138
119139 If `file_links` is empty, this is equivalent to `copytree()`. The rest of
@@ -134,35 +154,42 @@ def copytree_virtual(src, dst, file_links=[],
134154 link_targets = set ()
135155 for f in file_links :
136156 if os .path .isabs (f ):
137- raise ValueError (" copytree_virtual() failed: `%s': "
138- " absolute paths not allowed in file_links" % f )
157+ raise ValueError (f' copytree_virtual() failed: { f !r } : '
158+ f' absolute paths not allowed in file_links' )
139159
140160 target = os .path .join (src , f )
141161 if not os .path .exists (target ):
142- raise ValueError (" copytree_virtual() failed: `%s' "
143- " does not exist" % target )
162+ raise ValueError (f' copytree_virtual() failed: { target !r } '
163+ f' does not exist' )
144164
145165 if os .path .commonpath ([src , target ]) != src :
146- raise ValueError (" copytree_virtual() failed: "
147- "`%s' not under `%s'" % ( target , src ) )
166+ raise ValueError (f' copytree_virtual() failed: '
167+ f' { target !r } not under { src !r } ' )
148168
149169 link_targets .add (os .path .abspath (target ))
150170
171+ if '.' in file_links or '..' in file_links :
172+ raise ValueError (f"'.' or '..' are not allowed in file_links" )
173+
151174 if not file_links :
152175 ignore = None
153176 else :
154177 def ignore (dir , contents ):
155- return [ c for c in contents
156- if os .path .join (dir , c ) in link_targets ]
178+ return { c for c in contents
179+ if os .path .join (dir , c ) in link_targets }
157180
158181 # Copy to dst ignoring the file_links
159182 copytree (src , dst , symlinks , ignore ,
160- copy_function , ignore_dangling_symlinks )
183+ copy_function , ignore_dangling_symlinks , dirs_exist_ok )
161184
162185 # Now create the symlinks
163186 for f in link_targets :
164187 link_name = f .replace (src , dst )
165- os .symlink (f , link_name )
188+ try :
189+ os .symlink (f , link_name )
190+ except FileExistsError :
191+ if not dirs_exist_ok :
192+ raise
166193
167194
168195def rmtree (* args , max_retries = 3 , ** kwargs ):
0 commit comments