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