##// END OF EJS Templates
Move IPython specific functions from utils to IPython.paths
Thomas Kluyver -
Show More
@@ -0,0 +1,149 b''
1 import os.path
2 import shutil
3 import tempfile
4 from warnings import warn
5
6 import IPython
7 from IPython.utils.importstring import import_item
8 from IPython.utils.path import (
9 get_home_dir, get_xdg_dir, get_xdg_cache_dir, compress_user, _writable_dir,
10 ensure_dir_exists, fs_encoding, filefind
11 )
12 from IPython.utils import py3compat
13
14 def get_ipython_dir():
15 """Get the IPython directory for this platform and user.
16
17 This uses the logic in `get_home_dir` to find the home directory
18 and then adds .ipython to the end of the path.
19 """
20
21 env = os.environ
22 pjoin = os.path.join
23
24
25 ipdir_def = '.ipython'
26
27 home_dir = get_home_dir()
28 xdg_dir = get_xdg_dir()
29
30 # import pdb; pdb.set_trace() # dbg
31 if 'IPYTHON_DIR' in env:
32 warn('The environment variable IPYTHON_DIR is deprecated. '
33 'Please use IPYTHONDIR instead.')
34 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
35 if ipdir is None:
36 # not set explicitly, use ~/.ipython
37 ipdir = pjoin(home_dir, ipdir_def)
38 if xdg_dir:
39 # Several IPython versions (up to 1.x) defaulted to .config/ipython
40 # on Linux. We have decided to go back to using .ipython everywhere
41 xdg_ipdir = pjoin(xdg_dir, 'ipython')
42
43 if _writable_dir(xdg_ipdir):
44 cu = compress_user
45 if os.path.exists(ipdir):
46 warn(('Ignoring {0} in favour of {1}. Remove {0} to '
47 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
48 elif os.path.islink(xdg_ipdir):
49 warn(('{0} is deprecated. Move link to {1} to '
50 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
51 else:
52 warn('Moving {0} to {1}'.format(cu(xdg_ipdir), cu(ipdir)))
53 shutil.move(xdg_ipdir, ipdir)
54
55 ipdir = os.path.normpath(os.path.expanduser(ipdir))
56
57 if os.path.exists(ipdir) and not _writable_dir(ipdir):
58 # ipdir exists, but is not writable
59 warn("IPython dir '{0}' is not a writable location,"
60 " using a temp directory.".format(ipdir))
61 ipdir = tempfile.mkdtemp()
62 elif not os.path.exists(ipdir):
63 parent = os.path.dirname(ipdir)
64 if not _writable_dir(parent):
65 # ipdir does not exist and parent isn't writable
66 warn("IPython parent '{0}' is not a writable location,"
67 " using a temp directory.".format(parent))
68 ipdir = tempfile.mkdtemp()
69
70 return py3compat.cast_unicode(ipdir, fs_encoding)
71
72
73 def get_ipython_cache_dir():
74 """Get the cache directory it is created if it does not exist."""
75 xdgdir = get_xdg_cache_dir()
76 if xdgdir is None:
77 return get_ipython_dir()
78 ipdir = os.path.join(xdgdir, "ipython")
79 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
80 ensure_dir_exists(ipdir)
81 elif not _writable_dir(xdgdir):
82 return get_ipython_dir()
83
84 return py3compat.cast_unicode(ipdir, fs_encoding)
85
86
87 def get_ipython_package_dir():
88 """Get the base directory where IPython itself is installed."""
89 ipdir = os.path.dirname(IPython.__file__)
90 return py3compat.cast_unicode(ipdir, fs_encoding)
91
92
93 def get_ipython_module_path(module_str):
94 """Find the path to an IPython module in this version of IPython.
95
96 This will always find the version of the module that is in this importable
97 IPython package. This will always return the path to the ``.py``
98 version of the module.
99 """
100 if module_str == 'IPython':
101 return os.path.join(get_ipython_package_dir(), '__init__.py')
102 mod = import_item(module_str)
103 the_path = mod.__file__.replace('.pyc', '.py')
104 the_path = the_path.replace('.pyo', '.py')
105 return py3compat.cast_unicode(the_path, fs_encoding)
106
107 def locate_profile(profile='default'):
108 """Find the path to the folder associated with a given profile.
109
110 I.e. find $IPYTHONDIR/profile_whatever.
111 """
112 from IPython.core.profiledir import ProfileDir, ProfileDirError
113 try:
114 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
115 except ProfileDirError:
116 # IOError makes more sense when people are expecting a path
117 raise IOError("Couldn't find profile %r" % profile)
118 return pd.location
119
120 def get_security_file(filename, profile='default'):
121 """Return the absolute path of a security file given by filename and profile
122
123 This allows users and developers to find security files without
124 knowledge of the IPython directory structure. The search path
125 will be ['.', profile.security_dir]
126
127 Parameters
128 ----------
129
130 filename : str
131 The file to be found. If it is passed as an absolute path, it will
132 simply be returned.
133 profile : str [default: 'default']
134 The name of the profile to search. Leaving this unspecified
135 The file to be found. If it is passed as an absolute path, fname will
136 simply be returned.
137
138 Returns
139 -------
140 Raises :exc:`IOError` if file not found or returns absolute path to file.
141 """
142 # import here, because profiledir also imports from utils.path
143 from IPython.core.profiledir import ProfileDir
144 try:
145 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
146 except Exception:
147 # will raise ProfileDirError if no such profile
148 raise IOError("Profile %r not found")
149 return filefind(filename, ['.', pd.security_dir])
@@ -1,558 +1,455 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for path handling.
4 4 """
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 import os
10 10 import sys
11 11 import errno
12 12 import shutil
13 13 import random
14 14 import tempfile
15 15 import glob
16 16 from warnings import warn
17 17 from hashlib import md5
18 18
19 import IPython
20 19 from IPython.testing.skipdoctest import skip_doctest
21 20 from IPython.utils.process import system
22 from IPython.utils.importstring import import_item
23 21 from IPython.utils import py3compat
24 22 from IPython.utils.decorators import undoc
25 23
26 24 #-----------------------------------------------------------------------------
27 25 # Code
28 26 #-----------------------------------------------------------------------------
29 27
30 28 fs_encoding = sys.getfilesystemencoding()
31 29
32 30 def _writable_dir(path):
33 31 """Whether `path` is a directory, to which the user has write access."""
34 32 return os.path.isdir(path) and os.access(path, os.W_OK)
35 33
36 34 if sys.platform == 'win32':
37 35 @skip_doctest
38 36 def _get_long_path_name(path):
39 37 """Get a long path name (expand ~) on Windows using ctypes.
40 38
41 39 Examples
42 40 --------
43 41
44 42 >>> get_long_path_name('c:\\docume~1')
45 43 u'c:\\\\Documents and Settings'
46 44
47 45 """
48 46 try:
49 47 import ctypes
50 48 except ImportError:
51 49 raise ImportError('you need to have ctypes installed for this to work')
52 50 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
53 51 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
54 52 ctypes.c_uint ]
55 53
56 54 buf = ctypes.create_unicode_buffer(260)
57 55 rv = _GetLongPathName(path, buf, 260)
58 56 if rv == 0 or rv > 260:
59 57 return path
60 58 else:
61 59 return buf.value
62 60 else:
63 61 def _get_long_path_name(path):
64 62 """Dummy no-op."""
65 63 return path
66 64
67 65
68 66
69 67 def get_long_path_name(path):
70 68 """Expand a path into its long form.
71 69
72 70 On Windows this expands any ~ in the paths. On other platforms, it is
73 71 a null operation.
74 72 """
75 73 return _get_long_path_name(path)
76 74
77 75
78 76 def unquote_filename(name, win32=(sys.platform=='win32')):
79 77 """ On Windows, remove leading and trailing quotes from filenames.
80 78 """
81 79 if win32:
82 80 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
83 81 name = name[1:-1]
84 82 return name
85 83
86 84 def compress_user(path):
87 85 """Reverse of :func:`os.path.expanduser`
88 86 """
89 87 home = os.path.expanduser('~')
90 88 if path.startswith(home):
91 89 path = "~" + path[len(home):]
92 90 return path
93 91
94 92 def get_py_filename(name, force_win32=None):
95 93 """Return a valid python filename in the current directory.
96 94
97 95 If the given name is not a file, it adds '.py' and searches again.
98 96 Raises IOError with an informative message if the file isn't found.
99 97
100 98 On Windows, apply Windows semantics to the filename. In particular, remove
101 99 any quoting that has been applied to it. This option can be forced for
102 100 testing purposes.
103 101 """
104 102
105 103 name = os.path.expanduser(name)
106 104 if force_win32 is None:
107 105 win32 = (sys.platform == 'win32')
108 106 else:
109 107 win32 = force_win32
110 108 name = unquote_filename(name, win32=win32)
111 109 if not os.path.isfile(name) and not name.endswith('.py'):
112 110 name += '.py'
113 111 if os.path.isfile(name):
114 112 return name
115 113 else:
116 114 raise IOError('File `%r` not found.' % name)
117 115
118 116
119 117 def filefind(filename, path_dirs=None):
120 118 """Find a file by looking through a sequence of paths.
121 119
122 120 This iterates through a sequence of paths looking for a file and returns
123 121 the full, absolute path of the first occurence of the file. If no set of
124 122 path dirs is given, the filename is tested as is, after running through
125 123 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
126 124
127 125 filefind('myfile.txt')
128 126
129 127 will find the file in the current working dir, but::
130 128
131 129 filefind('~/myfile.txt')
132 130
133 131 Will find the file in the users home directory. This function does not
134 132 automatically try any paths, such as the cwd or the user's home directory.
135 133
136 134 Parameters
137 135 ----------
138 136 filename : str
139 137 The filename to look for.
140 138 path_dirs : str, None or sequence of str
141 139 The sequence of paths to look for the file in. If None, the filename
142 140 need to be absolute or be in the cwd. If a string, the string is
143 141 put into a sequence and the searched. If a sequence, walk through
144 142 each element and join with ``filename``, calling :func:`expandvars`
145 143 and :func:`expanduser` before testing for existence.
146 144
147 145 Returns
148 146 -------
149 147 Raises :exc:`IOError` or returns absolute path to file.
150 148 """
151 149
152 150 # If paths are quoted, abspath gets confused, strip them...
153 151 filename = filename.strip('"').strip("'")
154 152 # If the input is an absolute path, just check it exists
155 153 if os.path.isabs(filename) and os.path.isfile(filename):
156 154 return filename
157 155
158 156 if path_dirs is None:
159 157 path_dirs = ("",)
160 158 elif isinstance(path_dirs, py3compat.string_types):
161 159 path_dirs = (path_dirs,)
162 160
163 161 for path in path_dirs:
164 162 if path == '.': path = py3compat.getcwd()
165 163 testname = expand_path(os.path.join(path, filename))
166 164 if os.path.isfile(testname):
167 165 return os.path.abspath(testname)
168 166
169 167 raise IOError("File %r does not exist in any of the search paths: %r" %
170 168 (filename, path_dirs) )
171 169
172 170
173 171 class HomeDirError(Exception):
174 172 pass
175 173
176 174
177 175 def get_home_dir(require_writable=False):
178 176 """Return the 'home' directory, as a unicode string.
179 177
180 178 Uses os.path.expanduser('~'), and checks for writability.
181 179
182 180 See stdlib docs for how this is determined.
183 181 $HOME is first priority on *ALL* platforms.
184 182
185 183 Parameters
186 184 ----------
187 185
188 186 require_writable : bool [default: False]
189 187 if True:
190 188 guarantees the return value is a writable directory, otherwise
191 189 raises HomeDirError
192 190 if False:
193 191 The path is resolved, but it is not guaranteed to exist or be writable.
194 192 """
195 193
196 194 homedir = os.path.expanduser('~')
197 195 # Next line will make things work even when /home/ is a symlink to
198 196 # /usr/home as it is on FreeBSD, for example
199 197 homedir = os.path.realpath(homedir)
200 198
201 199 if not _writable_dir(homedir) and os.name == 'nt':
202 200 # expanduser failed, use the registry to get the 'My Documents' folder.
203 201 try:
204 202 try:
205 203 import winreg as wreg # Py 3
206 204 except ImportError:
207 205 import _winreg as wreg # Py 2
208 206 key = wreg.OpenKey(
209 207 wreg.HKEY_CURRENT_USER,
210 208 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
211 209 )
212 210 homedir = wreg.QueryValueEx(key,'Personal')[0]
213 211 key.Close()
214 212 except:
215 213 pass
216 214
217 215 if (not require_writable) or _writable_dir(homedir):
218 216 return py3compat.cast_unicode(homedir, fs_encoding)
219 217 else:
220 218 raise HomeDirError('%s is not a writable dir, '
221 219 'set $HOME environment variable to override' % homedir)
222 220
223 221 def get_xdg_dir():
224 222 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
225 223
226 224 This is only for non-OS X posix (Linux,Unix,etc.) systems.
227 225 """
228 226
229 227 env = os.environ
230 228
231 229 if os.name == 'posix' and sys.platform != 'darwin':
232 230 # Linux, Unix, AIX, etc.
233 231 # use ~/.config if empty OR not set
234 232 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
235 233 if xdg and _writable_dir(xdg):
236 234 return py3compat.cast_unicode(xdg, fs_encoding)
237 235
238 236 return None
239 237
240 238
241 239 def get_xdg_cache_dir():
242 240 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
243 241
244 242 This is only for non-OS X posix (Linux,Unix,etc.) systems.
245 243 """
246 244
247 245 env = os.environ
248 246
249 247 if os.name == 'posix' and sys.platform != 'darwin':
250 248 # Linux, Unix, AIX, etc.
251 249 # use ~/.cache if empty OR not set
252 250 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
253 251 if xdg and _writable_dir(xdg):
254 252 return py3compat.cast_unicode(xdg, fs_encoding)
255 253
256 254 return None
257 255
258 256
257 @undoc
259 258 def get_ipython_dir():
260 """Get the IPython directory for this platform and user.
261
262 This uses the logic in `get_home_dir` to find the home directory
263 and then adds .ipython to the end of the path.
264 """
265
266 env = os.environ
267 pjoin = os.path.join
268
269
270 ipdir_def = '.ipython'
271
272 home_dir = get_home_dir()
273 xdg_dir = get_xdg_dir()
274
275 # import pdb; pdb.set_trace() # dbg
276 if 'IPYTHON_DIR' in env:
277 warn('The environment variable IPYTHON_DIR is deprecated. '
278 'Please use IPYTHONDIR instead.')
279 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
280 if ipdir is None:
281 # not set explicitly, use ~/.ipython
282 ipdir = pjoin(home_dir, ipdir_def)
283 if xdg_dir:
284 # Several IPython versions (up to 1.x) defaulted to .config/ipython
285 # on Linux. We have decided to go back to using .ipython everywhere
286 xdg_ipdir = pjoin(xdg_dir, 'ipython')
287
288 if _writable_dir(xdg_ipdir):
289 cu = compress_user
290 if os.path.exists(ipdir):
291 warn(('Ignoring {0} in favour of {1}. Remove {0} to '
292 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
293 elif os.path.islink(xdg_ipdir):
294 warn(('{0} is deprecated. Move link to {1} to '
295 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
296 else:
297 warn('Moving {0} to {1}'.format(cu(xdg_ipdir), cu(ipdir)))
298 shutil.move(xdg_ipdir, ipdir)
299
300 ipdir = os.path.normpath(os.path.expanduser(ipdir))
301
302 if os.path.exists(ipdir) and not _writable_dir(ipdir):
303 # ipdir exists, but is not writable
304 warn("IPython dir '{0}' is not a writable location,"
305 " using a temp directory.".format(ipdir))
306 ipdir = tempfile.mkdtemp()
307 elif not os.path.exists(ipdir):
308 parent = os.path.dirname(ipdir)
309 if not _writable_dir(parent):
310 # ipdir does not exist and parent isn't writable
311 warn("IPython parent '{0}' is not a writable location,"
312 " using a temp directory.".format(parent))
313 ipdir = tempfile.mkdtemp()
314
315 return py3compat.cast_unicode(ipdir, fs_encoding)
316
317
318 def get_ipython_cache_dir():
319 """Get the cache directory it is created if it does not exist."""
320 xdgdir = get_xdg_cache_dir()
321 if xdgdir is None:
322 return get_ipython_dir()
323 ipdir = os.path.join(xdgdir, "ipython")
324 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
325 ensure_dir_exists(ipdir)
326 elif not _writable_dir(xdgdir):
259 warn("get_ipython_dir has moved to the IPython.paths module")
260 from IPython.paths import get_ipython_dir
327 261 return get_ipython_dir()
328 262
329 return py3compat.cast_unicode(ipdir, fs_encoding)
330
263 @undoc
264 def get_ipython_cache_dir():
265 warn("get_ipython_cache_dir has moved to the IPython.paths module")
266 from IPython.paths import get_ipython_cache_dir
267 return get_ipython_cache_dir()
331 268
269 @undoc
332 270 def get_ipython_package_dir():
333 """Get the base directory where IPython itself is installed."""
334 ipdir = os.path.dirname(IPython.__file__)
335 return py3compat.cast_unicode(ipdir, fs_encoding)
336
271 warn("get_ipython_package_dir has moved to the IPython.paths module")
272 from IPython.paths import get_ipython_package_dir
273 return get_ipython_package_dir()
337 274
275 @undoc
338 276 def get_ipython_module_path(module_str):
339 """Find the path to an IPython module in this version of IPython.
340
341 This will always find the version of the module that is in this importable
342 IPython package. This will always return the path to the ``.py``
343 version of the module.
344 """
345 if module_str == 'IPython':
346 return os.path.join(get_ipython_package_dir(), '__init__.py')
347 mod = import_item(module_str)
348 the_path = mod.__file__.replace('.pyc', '.py')
349 the_path = the_path.replace('.pyo', '.py')
350 return py3compat.cast_unicode(the_path, fs_encoding)
277 warn("get_ipython_module_path has moved to the IPython.paths module")
278 from IPython.paths import get_ipython_module_path
279 return get_ipython_module_path(module_str)
351 280
281 @undoc
352 282 def locate_profile(profile='default'):
353 """Find the path to the folder associated with a given profile.
354
355 I.e. find $IPYTHONDIR/profile_whatever.
356 """
357 from IPython.core.profiledir import ProfileDir, ProfileDirError
358 try:
359 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
360 except ProfileDirError:
361 # IOError makes more sense when people are expecting a path
362 raise IOError("Couldn't find profile %r" % profile)
363 return pd.location
283 warn("locate_profile has moved to the IPython.paths module")
284 from IPython.paths import locate_profile
285 return locate_profile(profile=profile)
364 286
365 287 def expand_path(s):
366 288 """Expand $VARS and ~names in a string, like a shell
367 289
368 290 :Examples:
369 291
370 292 In [2]: os.environ['FOO']='test'
371 293
372 294 In [3]: expand_path('variable FOO is $FOO')
373 295 Out[3]: 'variable FOO is test'
374 296 """
375 297 # This is a pretty subtle hack. When expand user is given a UNC path
376 298 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
377 299 # the $ to get (\\server\share\%username%). I think it considered $
378 300 # alone an empty var. But, we need the $ to remains there (it indicates
379 301 # a hidden share).
380 302 if os.name=='nt':
381 303 s = s.replace('$\\', 'IPYTHON_TEMP')
382 304 s = os.path.expandvars(os.path.expanduser(s))
383 305 if os.name=='nt':
384 306 s = s.replace('IPYTHON_TEMP', '$\\')
385 307 return s
386 308
387 309
388 310 def unescape_glob(string):
389 311 """Unescape glob pattern in `string`."""
390 312 def unescape(s):
391 313 for pattern in '*[]!?':
392 314 s = s.replace(r'\{0}'.format(pattern), pattern)
393 315 return s
394 316 return '\\'.join(map(unescape, string.split('\\\\')))
395 317
396 318
397 319 def shellglob(args):
398 320 """
399 321 Do glob expansion for each element in `args` and return a flattened list.
400 322
401 323 Unmatched glob pattern will remain as-is in the returned list.
402 324
403 325 """
404 326 expanded = []
405 327 # Do not unescape backslash in Windows as it is interpreted as
406 328 # path separator:
407 329 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
408 330 for a in args:
409 331 expanded.extend(glob.glob(a) or [unescape(a)])
410 332 return expanded
411 333
412 334
413 335 def target_outdated(target,deps):
414 336 """Determine whether a target is out of date.
415 337
416 338 target_outdated(target,deps) -> 1/0
417 339
418 340 deps: list of filenames which MUST exist.
419 341 target: single filename which may or may not exist.
420 342
421 343 If target doesn't exist or is older than any file listed in deps, return
422 344 true, otherwise return false.
423 345 """
424 346 try:
425 347 target_time = os.path.getmtime(target)
426 348 except os.error:
427 349 return 1
428 350 for dep in deps:
429 351 dep_time = os.path.getmtime(dep)
430 352 if dep_time > target_time:
431 353 #print "For target",target,"Dep failed:",dep # dbg
432 354 #print "times (dep,tar):",dep_time,target_time # dbg
433 355 return 1
434 356 return 0
435 357
436 358
437 359 def target_update(target,deps,cmd):
438 360 """Update a target with a given command given a list of dependencies.
439 361
440 362 target_update(target,deps,cmd) -> runs cmd if target is outdated.
441 363
442 364 This is just a wrapper around target_outdated() which calls the given
443 365 command if target is outdated."""
444 366
445 367 if target_outdated(target,deps):
446 368 system(cmd)
447 369
448 370 @undoc
449 371 def filehash(path):
450 372 """Make an MD5 hash of a file, ignoring any differences in line
451 373 ending characters."""
452 374 warn("filehash() is deprecated")
453 375 with open(path, "rU") as f:
454 376 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
455 377
378 @undoc
456 379 def get_security_file(filename, profile='default'):
457 """Return the absolute path of a security file given by filename and profile
458
459 This allows users and developers to find security files without
460 knowledge of the IPython directory structure. The search path
461 will be ['.', profile.security_dir]
462
463 Parameters
464 ----------
465
466 filename : str
467 The file to be found. If it is passed as an absolute path, it will
468 simply be returned.
469 profile : str [default: 'default']
470 The name of the profile to search. Leaving this unspecified
471 The file to be found. If it is passed as an absolute path, fname will
472 simply be returned.
473
474 Returns
475 -------
476 Raises :exc:`IOError` if file not found or returns absolute path to file.
477 """
478 # import here, because profiledir also imports from utils.path
479 from IPython.core.profiledir import ProfileDir
480 try:
481 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
482 except Exception:
483 # will raise ProfileDirError if no such profile
484 raise IOError("Profile %r not found")
485 return filefind(filename, ['.', pd.security_dir])
380 warn("get_security_file has moved to the IPython.paths module")
381 from IPython.paths import get_security_file
382 return get_security_file(filename, profile=profile)
486 383
487 384
488 385 ENOLINK = 1998
489 386
490 387 def link(src, dst):
491 388 """Hard links ``src`` to ``dst``, returning 0 or errno.
492 389
493 390 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
494 391 supported by the operating system.
495 392 """
496 393
497 394 if not hasattr(os, "link"):
498 395 return ENOLINK
499 396 link_errno = 0
500 397 try:
501 398 os.link(src, dst)
502 399 except OSError as e:
503 400 link_errno = e.errno
504 401 return link_errno
505 402
506 403
507 404 def link_or_copy(src, dst):
508 405 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
509 406
510 407 Attempts to maintain the semantics of ``shutil.copy``.
511 408
512 409 Because ``os.link`` does not overwrite files, a unique temporary file
513 410 will be used if the target already exists, then that file will be moved
514 411 into place.
515 412 """
516 413
517 414 if os.path.isdir(dst):
518 415 dst = os.path.join(dst, os.path.basename(src))
519 416
520 417 link_errno = link(src, dst)
521 418 if link_errno == errno.EEXIST:
522 419 if os.stat(src).st_ino == os.stat(dst).st_ino:
523 420 # dst is already a hard link to the correct file, so we don't need
524 421 # to do anything else. If we try to link and rename the file
525 422 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
526 423 return
527 424
528 425 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
529 426 try:
530 427 link_or_copy(src, new_dst)
531 428 except:
532 429 try:
533 430 os.remove(new_dst)
534 431 except OSError:
535 432 pass
536 433 raise
537 434 os.rename(new_dst, dst)
538 435 elif link_errno != 0:
539 436 # Either link isn't supported, or the filesystem doesn't support
540 437 # linking, or 'src' and 'dst' are on different filesystems.
541 438 shutil.copy(src, dst)
542 439
543 440 def ensure_dir_exists(path, mode=0o755):
544 441 """ensure that a directory exists
545 442
546 443 If it doesn't exist, try to create it and protect against a race condition
547 444 if another process is doing the same.
548 445
549 446 The default permissions are 755, which differ from os.makedirs default of 777.
550 447 """
551 448 if not os.path.exists(path):
552 449 try:
553 450 os.makedirs(path, mode=mode)
554 451 except OSError as e:
555 452 if e.errno != errno.EEXIST:
556 453 raise
557 454 elif not os.path.isdir(path):
558 455 raise IOError("%r exists but is not a directory" % path)
General Comments 0
You need to be logged in to leave comments. Login now