##// END OF EJS Templates
Emit deprecations warnings
Matthias Bussonnier -
Show More
@@ -1,396 +1,407
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 glob
15 import warnings
15 16
16 17 from IPython.utils.process import system
17 18
18 19 #-----------------------------------------------------------------------------
19 20 # Code
20 21 #-----------------------------------------------------------------------------
21 22 fs_encoding = sys.getfilesystemencoding()
22 23
23 24 def _writable_dir(path):
24 25 """Whether `path` is a directory, to which the user has write access."""
25 26 return os.path.isdir(path) and os.access(path, os.W_OK)
26 27
27 28 if sys.platform == 'win32':
28 29 def _get_long_path_name(path):
29 30 """Get a long path name (expand ~) on Windows using ctypes.
30 31
31 32 Examples
32 33 --------
33 34
34 35 >>> get_long_path_name('c:\\\\docume~1')
35 36 'c:\\\\Documents and Settings'
36 37
37 38 """
38 39 try:
39 40 import ctypes
40 41 except ImportError as e:
41 42 raise ImportError('you need to have ctypes installed for this to work') from e
42 43 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
43 44 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
44 45 ctypes.c_uint ]
45 46
46 47 buf = ctypes.create_unicode_buffer(260)
47 48 rv = _GetLongPathName(path, buf, 260)
48 49 if rv == 0 or rv > 260:
49 50 return path
50 51 else:
51 52 return buf.value
52 53 else:
53 54 def _get_long_path_name(path):
54 55 """Dummy no-op."""
55 56 return path
56 57
57 58
58 59
59 60 def get_long_path_name(path):
60 61 """Expand a path into its long form.
61 62
62 63 On Windows this expands any ~ in the paths. On other platforms, it is
63 64 a null operation.
64 65 """
65 66 return _get_long_path_name(path)
66 67
67 68
68 69 def compress_user(path):
69 70 """Reverse of :func:`os.path.expanduser`
70 71 """
71 72 home = os.path.expanduser('~')
72 73 if path.startswith(home):
73 74 path = "~" + path[len(home):]
74 75 return path
75 76
76 77 def get_py_filename(name):
77 78 """Return a valid python filename in the current directory.
78 79
79 80 If the given name is not a file, it adds '.py' and searches again.
80 81 Raises IOError with an informative message if the file isn't found.
81 82 """
82 83
83 84 name = os.path.expanduser(name)
84 85 if os.path.isfile(name):
85 86 return name
86 87 if not name.endswith(".py"):
87 88 py_name = name + ".py"
88 89 if os.path.isfile(py_name):
89 90 return py_name
90 91 raise IOError("File `%r` not found." % name)
91 92
92 93
93 94 def filefind(filename: str, path_dirs=None) -> str:
94 95 """Find a file by looking through a sequence of paths.
95 96
96 97 This iterates through a sequence of paths looking for a file and returns
97 98 the full, absolute path of the first occurrence of the file. If no set of
98 99 path dirs is given, the filename is tested as is, after running through
99 100 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
100 101
101 102 filefind('myfile.txt')
102 103
103 104 will find the file in the current working dir, but::
104 105
105 106 filefind('~/myfile.txt')
106 107
107 108 Will find the file in the users home directory. This function does not
108 109 automatically try any paths, such as the cwd or the user's home directory.
109 110
110 111 Parameters
111 112 ----------
112 113 filename : str
113 114 The filename to look for.
114 115 path_dirs : str, None or sequence of str
115 116 The sequence of paths to look for the file in. If None, the filename
116 117 need to be absolute or be in the cwd. If a string, the string is
117 118 put into a sequence and the searched. If a sequence, walk through
118 119 each element and join with ``filename``, calling :func:`expandvars`
119 120 and :func:`expanduser` before testing for existence.
120 121
121 122 Returns
122 123 -------
123 124 path : str
124 125 returns absolute path to file.
125 126
126 127 Raises
127 128 ------
128 129 IOError
129 130 """
130 131
131 132 # If paths are quoted, abspath gets confused, strip them...
132 133 filename = filename.strip('"').strip("'")
133 134 # If the input is an absolute path, just check it exists
134 135 if os.path.isabs(filename) and os.path.isfile(filename):
135 136 return filename
136 137
137 138 if path_dirs is None:
138 139 path_dirs = ("",)
139 140 elif isinstance(path_dirs, str):
140 141 path_dirs = (path_dirs,)
141 142
142 143 for path in path_dirs:
143 144 if path == '.': path = os.getcwd()
144 145 testname = expand_path(os.path.join(path, filename))
145 146 if os.path.isfile(testname):
146 147 return os.path.abspath(testname)
147 148
148 149 raise IOError("File %r does not exist in any of the search paths: %r" %
149 150 (filename, path_dirs) )
150 151
151 152
152 153 class HomeDirError(Exception):
153 154 pass
154 155
155 156
156 157 def get_home_dir(require_writable=False) -> str:
157 158 """Return the 'home' directory, as a unicode string.
158 159
159 160 Uses os.path.expanduser('~'), and checks for writability.
160 161
161 162 See stdlib docs for how this is determined.
162 163 For Python <3.8, $HOME is first priority on *ALL* platforms.
163 164 For Python >=3.8 on Windows, %HOME% is no longer considered.
164 165
165 166 Parameters
166 167 ----------
167 168 require_writable : bool [default: False]
168 169 if True:
169 170 guarantees the return value is a writable directory, otherwise
170 171 raises HomeDirError
171 172 if False:
172 173 The path is resolved, but it is not guaranteed to exist or be writable.
173 174 """
174 175
175 176 homedir = os.path.expanduser('~')
176 177 # Next line will make things work even when /home/ is a symlink to
177 178 # /usr/home as it is on FreeBSD, for example
178 179 homedir = os.path.realpath(homedir)
179 180
180 181 if not _writable_dir(homedir) and os.name == 'nt':
181 182 # expanduser failed, use the registry to get the 'My Documents' folder.
182 183 try:
183 184 import winreg as wreg
184 185 with wreg.OpenKey(
185 186 wreg.HKEY_CURRENT_USER,
186 187 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
187 188 ) as key:
188 189 homedir = wreg.QueryValueEx(key,'Personal')[0]
189 190 except:
190 191 pass
191 192
192 193 if (not require_writable) or _writable_dir(homedir):
193 194 assert isinstance(homedir, str), "Homedir should be unicode not bytes"
194 195 return homedir
195 196 else:
196 197 raise HomeDirError('%s is not a writable dir, '
197 198 'set $HOME environment variable to override' % homedir)
198 199
199 200 def get_xdg_dir():
200 201 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
201 202
202 203 This is only for non-OS X posix (Linux,Unix,etc.) systems.
203 204 """
204 205
205 206 env = os.environ
206 207
207 208 if os.name == "posix":
208 209 # Linux, Unix, AIX, etc.
209 210 # use ~/.config if empty OR not set
210 211 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
211 212 if xdg and _writable_dir(xdg):
212 213 assert isinstance(xdg, str)
213 214 return xdg
214 215
215 216 return None
216 217
217 218
218 219 def get_xdg_cache_dir():
219 220 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
220 221
221 222 This is only for non-OS X posix (Linux,Unix,etc.) systems.
222 223 """
223 224
224 225 env = os.environ
225 226
226 227 if os.name == "posix":
227 228 # Linux, Unix, AIX, etc.
228 229 # use ~/.cache if empty OR not set
229 230 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
230 231 if xdg and _writable_dir(xdg):
231 232 assert isinstance(xdg, str)
232 233 return xdg
233 234
234 235 return None
235 236
236 237
237 238 def expand_path(s):
238 239 """Expand $VARS and ~names in a string, like a shell
239 240
240 241 :Examples:
241 242
242 243 In [2]: os.environ['FOO']='test'
243 244
244 245 In [3]: expand_path('variable FOO is $FOO')
245 246 Out[3]: 'variable FOO is test'
246 247 """
247 248 # This is a pretty subtle hack. When expand user is given a UNC path
248 249 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
249 250 # the $ to get (\\server\share\%username%). I think it considered $
250 251 # alone an empty var. But, we need the $ to remains there (it indicates
251 252 # a hidden share).
252 253 if os.name=='nt':
253 254 s = s.replace('$\\', 'IPYTHON_TEMP')
254 255 s = os.path.expandvars(os.path.expanduser(s))
255 256 if os.name=='nt':
256 257 s = s.replace('IPYTHON_TEMP', '$\\')
257 258 return s
258 259
259 260
260 261 def unescape_glob(string):
261 262 """Unescape glob pattern in `string`."""
262 263 def unescape(s):
263 264 for pattern in '*[]!?':
264 265 s = s.replace(r'\{0}'.format(pattern), pattern)
265 266 return s
266 267 return '\\'.join(map(unescape, string.split('\\\\')))
267 268
268 269
269 270 def shellglob(args):
270 271 """
271 272 Do glob expansion for each element in `args` and return a flattened list.
272 273
273 274 Unmatched glob pattern will remain as-is in the returned list.
274 275
275 276 """
276 277 expanded = []
277 278 # Do not unescape backslash in Windows as it is interpreted as
278 279 # path separator:
279 280 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
280 281 for a in args:
281 282 expanded.extend(glob.glob(a) or [unescape(a)])
282 283 return expanded
283 284
284 285
285 286 def target_outdated(target,deps):
286 287 """Determine whether a target is out of date.
287 288
288 289 target_outdated(target,deps) -> 1/0
289 290
290 291 deps: list of filenames which MUST exist.
291 292 target: single filename which may or may not exist.
292 293
293 294 If target doesn't exist or is older than any file listed in deps, return
294 295 true, otherwise return false.
295 296
296 297 .. deprecated:: 8.22
297 298 """
299 warnings.warn(
300 "`target_outdated` is deprecated since IPython 8.22 and will be removed in future versions",
301 DeprecationWarning,
302 stacklevel=2,
303 )
298 304 try:
299 305 target_time = os.path.getmtime(target)
300 306 except os.error:
301 307 return 1
302 308 for dep in deps:
303 309 dep_time = os.path.getmtime(dep)
304 310 if dep_time > target_time:
305 311 #print "For target",target,"Dep failed:",dep # dbg
306 312 #print "times (dep,tar):",dep_time,target_time # dbg
307 313 return 1
308 314 return 0
309 315
310 316
311 317 def target_update(target,deps,cmd):
312 318 """Update a target with a given command given a list of dependencies.
313 319
314 320 target_update(target,deps,cmd) -> runs cmd if target is outdated.
315 321
316 322 This is just a wrapper around target_outdated() which calls the given
317 323 command if target is outdated.
318 324
319 325 .. deprecated:: 8.22
320 326 """
321 327
322 if target_outdated(target,deps):
328 warnings.warn(
329 "`target_update` is deprecated since IPython 8.22 and will be removed in future versions",
330 DeprecationWarning,
331 stacklevel=2,
332 )
333 if target_outdated(target, deps):
323 334 system(cmd)
324 335
325 336
326 337 ENOLINK = 1998
327 338
328 339 def link(src, dst):
329 340 """Hard links ``src`` to ``dst``, returning 0 or errno.
330 341
331 342 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
332 343 supported by the operating system.
333 344 """
334 345
335 346 if not hasattr(os, "link"):
336 347 return ENOLINK
337 348 link_errno = 0
338 349 try:
339 350 os.link(src, dst)
340 351 except OSError as e:
341 352 link_errno = e.errno
342 353 return link_errno
343 354
344 355
345 356 def link_or_copy(src, dst):
346 357 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
347 358
348 359 Attempts to maintain the semantics of ``shutil.copy``.
349 360
350 361 Because ``os.link`` does not overwrite files, a unique temporary file
351 362 will be used if the target already exists, then that file will be moved
352 363 into place.
353 364 """
354 365
355 366 if os.path.isdir(dst):
356 367 dst = os.path.join(dst, os.path.basename(src))
357 368
358 369 link_errno = link(src, dst)
359 370 if link_errno == errno.EEXIST:
360 371 if os.stat(src).st_ino == os.stat(dst).st_ino:
361 372 # dst is already a hard link to the correct file, so we don't need
362 373 # to do anything else. If we try to link and rename the file
363 374 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
364 375 return
365 376
366 377 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
367 378 try:
368 379 link_or_copy(src, new_dst)
369 380 except:
370 381 try:
371 382 os.remove(new_dst)
372 383 except OSError:
373 384 pass
374 385 raise
375 386 os.rename(new_dst, dst)
376 387 elif link_errno != 0:
377 388 # Either link isn't supported, or the filesystem doesn't support
378 389 # linking, or 'src' and 'dst' are on different filesystems.
379 390 shutil.copy(src, dst)
380 391
381 392 def ensure_dir_exists(path, mode=0o755):
382 393 """ensure that a directory exists
383 394
384 395 If it doesn't exist, try to create it and protect against a race condition
385 396 if another process is doing the same.
386 397
387 398 The default permissions are 755, which differ from os.makedirs default of 777.
388 399 """
389 400 if not os.path.exists(path):
390 401 try:
391 402 os.makedirs(path, mode=mode)
392 403 except OSError as e:
393 404 if e.errno != errno.EEXIST:
394 405 raise
395 406 elif not os.path.isdir(path):
396 407 raise IOError("%r exists but is not a directory" % path)
General Comments 0
You need to be logged in to leave comments. Login now