Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/utils/path.py: 17%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

148 statements  

1""" 

2Utilities for path handling. 

3""" 

4 

5# Copyright (c) IPython Development Team. 

6# Distributed under the terms of the Modified BSD License. 

7 

8import os 

9import sys 

10import errno 

11import warnings 

12 

13#----------------------------------------------------------------------------- 

14# Code 

15#----------------------------------------------------------------------------- 

16fs_encoding = sys.getfilesystemencoding() 

17 

18def _writable_dir(path: str) -> bool: 

19 """Whether `path` is a directory, to which the user has write access.""" 

20 return os.path.isdir(path) and os.access(path, os.W_OK) 

21 

22if sys.platform == 'win32': 

23 def _get_long_path_name(path): 

24 """Get a long path name (expand ~) on Windows using ctypes. 

25 

26 Examples 

27 -------- 

28 

29 >>> get_long_path_name('c:\\\\docume~1') 

30 'c:\\\\Documents and Settings' 

31 

32 """ 

33 try: 

34 import ctypes 

35 except ImportError as e: 

36 raise ImportError('you need to have ctypes installed for this to work') from e 

37 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW 

38 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p, 

39 ctypes.c_uint ] 

40 

41 buf = ctypes.create_unicode_buffer(260) 

42 rv = _GetLongPathName(path, buf, 260) 

43 if rv == 0 or rv > 260: 

44 return path 

45 else: 

46 return buf.value 

47else: 

48 def _get_long_path_name(path): 

49 """Dummy no-op.""" 

50 return path 

51 

52 

53 

54def get_long_path_name(path): 

55 """Expand a path into its long form. 

56 

57 On Windows this expands any ~ in the paths. On other platforms, it is 

58 a null operation. 

59 """ 

60 return _get_long_path_name(path) 

61 

62 

63def compress_user(path: str) -> str: 

64 """Reverse of :func:`os.path.expanduser`""" 

65 home = os.path.expanduser("~") 

66 # Windows filesystems are case-insensitive and mix separators, so compare 

67 # with normcase (a no-op on POSIX). It preserves length, so len(prefix) 

68 # still indexes the original, un-normcased path correctly below. 

69 if os.path.normcase(path) == os.path.normcase(home): 

70 return "~" 

71 # Compare against home + separator, so that a path which merely shares a 

72 # prefix with home (/home/alice-backup vs /home/alice) is left alone. 

73 prefix = os.path.join(home, "") 

74 if os.path.normcase(path).startswith(os.path.normcase(prefix)): 

75 path = "~" + os.sep + path[len(prefix) :] 

76 return path 

77 

78def get_py_filename(name): 

79 """Return a valid python filename in the current directory. 

80 

81 If the given name is not a file, it adds '.py' and searches again. 

82 Raises IOError with an informative message if the file isn't found. 

83 """ 

84 

85 name = os.path.expanduser(name) 

86 if os.path.isfile(name): 

87 return name 

88 if not name.endswith(".py"): 

89 py_name = name + ".py" 

90 if os.path.isfile(py_name): 

91 return py_name 

92 raise OSError("File `%r` not found." % name) 

93 

94 

95def filefind(filename: str, path_dirs=None) -> str: 

96 """Find a file by looking through a sequence of paths. 

97 

98 This iterates through a sequence of paths looking for a file and returns 

99 the full, absolute path of the first occurrence of the file. If no set of 

100 path dirs is given, the filename is tested as is, after running through 

101 :func:`expandvars` and :func:`expanduser`. Thus a simple call:: 

102 

103 filefind('myfile.txt') 

104 

105 will find the file in the current working dir, but:: 

106 

107 filefind('~/myfile.txt') 

108 

109 Will find the file in the users home directory. This function does not 

110 automatically try any paths, such as the cwd or the user's home directory. 

111 

112 Parameters 

113 ---------- 

114 filename : str 

115 The filename to look for. 

116 path_dirs : str, None or sequence of str 

117 The sequence of paths to look for the file in. If None, the filename 

118 need to be absolute or be in the cwd. If a string, the string is 

119 put into a sequence and the searched. If a sequence, walk through 

120 each element and join with ``filename``, calling :func:`expandvars` 

121 and :func:`expanduser` before testing for existence. 

122 

123 Returns 

124 ------- 

125 path : str 

126 returns absolute path to file. 

127 

128 Raises 

129 ------ 

130 IOError 

131 """ 

132 

133 # If paths are quoted, abspath gets confused, strip them... 

134 filename = filename.strip('"').strip("'") 

135 # If the input is an absolute path, just check it exists 

136 if os.path.isabs(filename) and os.path.isfile(filename): 

137 return filename 

138 

139 if path_dirs is None: 

140 path_dirs = ("",) 

141 elif isinstance(path_dirs, str): 

142 path_dirs = (path_dirs,) 

143 

144 for path in path_dirs: 

145 if path == '.': path = os.getcwd() 

146 testname = expand_path(os.path.join(path, filename)) 

147 if os.path.isfile(testname): 

148 return os.path.abspath(testname) 

149 

150 raise OSError("File %r does not exist in any of the search paths: %r" % 

151 (filename, path_dirs) ) 

152 

153 

154class HomeDirError(Exception): 

155 pass 

156 

157 

158def get_home_dir(require_writable: bool=False) -> str: 

159 """Return the 'home' directory, as a unicode string. 

160 

161 Uses os.path.expanduser('~'), and checks for writability. 

162 

163 See stdlib docs for how this is determined. 

164 For Python <3.8, $HOME is first priority on *ALL* platforms. 

165 For Python >=3.8 on Windows, %HOME% is no longer considered. 

166 

167 Parameters 

168 ---------- 

169 require_writable : bool [default: False] 

170 if True: 

171 guarantees the return value is a writable directory, otherwise 

172 raises HomeDirError 

173 if False: 

174 The path is resolved, but it is not guaranteed to exist or be writable. 

175 """ 

176 

177 homedir = os.path.expanduser('~') 

178 # Next line will make things work even when /home/ is a symlink to 

179 # /usr/home as it is on FreeBSD, for example 

180 homedir = os.path.realpath(homedir) 

181 

182 if not _writable_dir(homedir) and os.name == 'nt': 

183 # expanduser failed, use the registry to get the 'My Documents' folder. 

184 try: 

185 import winreg as wreg 

186 with wreg.OpenKey( 

187 wreg.HKEY_CURRENT_USER, 

188 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" 

189 ) as key: 

190 homedir = wreg.QueryValueEx(key,'Personal')[0] 

191 except Exception: 

192 pass 

193 

194 if (not require_writable) or _writable_dir(homedir): 

195 assert isinstance(homedir, str), "Homedir should be unicode not bytes" 

196 return homedir 

197 else: 

198 raise HomeDirError('%s is not a writable dir, ' 

199 'set $HOME environment variable to override' % homedir) 

200 

201def get_xdg_dir() -> str | None: 

202 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None. 

203 

204 This is only for non-OS X posix (Linux,Unix,etc.) systems. 

205 """ 

206 

207 env = os.environ 

208 

209 if os.name == "posix": 

210 # Linux, Unix, AIX, etc. 

211 # use ~/.config if empty OR not set 

212 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config') 

213 if xdg and _writable_dir(xdg): 

214 assert isinstance(xdg, str) 

215 return xdg 

216 

217 return None 

218 

219 

220def get_xdg_cache_dir(): 

221 """Return the XDG_CACHE_HOME, if it is defined and exists, else None. 

222 

223 This is only for non-OS X posix (Linux,Unix,etc.) systems. 

224 """ 

225 

226 env = os.environ 

227 

228 if os.name == "posix": 

229 # Linux, Unix, AIX, etc. 

230 # use ~/.cache if empty OR not set 

231 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache') 

232 if xdg and _writable_dir(xdg): 

233 assert isinstance(xdg, str) 

234 return xdg 

235 

236 return None 

237 

238 

239def expand_path(s: str) -> str: 

240 """Expand $VARS and ~names in a string, like a shell 

241 

242 :Examples: 

243 

244 In [2]: os.environ['FOO']='test' 

245 

246 In [3]: expand_path('variable FOO is $FOO') 

247 Out[3]: 'variable FOO is test' 

248 """ 

249 # This is a pretty subtle hack. When expand user is given a UNC path 

250 # on Windows (\\server\share$\%username%), os.path.expandvars, removes 

251 # the $ to get (\\server\share\%username%). I think it considered $ 

252 # alone an empty var. But, we need the $ to remains there (it indicates 

253 # a hidden share). 

254 if os.name=='nt': 

255 s = s.replace('$\\', 'IPYTHON_TEMP') 

256 s = os.path.expandvars(os.path.expanduser(s)) 

257 if os.name=='nt': 

258 s = s.replace('IPYTHON_TEMP', '$\\') 

259 return s 

260 

261 

262def unescape_glob(string): 

263 """Unescape glob pattern in `string`.""" 

264 def unescape(s): 

265 for pattern in '*[]!?': 

266 s = s.replace(fr'\{pattern}', pattern) 

267 return s 

268 return '\\'.join(map(unescape, string.split('\\\\'))) 

269 

270 

271def shellglob(args): 

272 """ 

273 Do glob expansion for each element in `args` and return a flattened list. 

274 

275 Unmatched glob pattern will remain as-is in the returned list. 

276 

277 """ 

278 expanded = [] 

279 # Do not unescape backslash in Windows as it is interpreted as 

280 # path separator: 

281 import glob 

282 

283 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x 

284 for a in args: 

285 expanded.extend(glob.glob(a) or [unescape(a)]) 

286 return expanded 

287 

288ENOLINK = 1998 

289 

290def link(src, dst): 

291 """Hard links ``src`` to ``dst``, returning 0 or errno. 

292 

293 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't 

294 supported by the operating system. 

295 """ 

296 

297 if not hasattr(os, "link"): 

298 return ENOLINK 

299 link_errno = 0 

300 try: 

301 os.link(src, dst) 

302 except OSError as e: 

303 link_errno = e.errno 

304 return link_errno 

305 

306 

307def link_or_copy(src, dst): 

308 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails. 

309 

310 Attempts to maintain the semantics of ``shutil.copy``. 

311 

312 Because ``os.link`` does not overwrite files, a unique temporary file 

313 will be used if the target already exists, then that file will be moved 

314 into place. 

315 """ 

316 

317 if os.path.isdir(dst): 

318 dst = os.path.join(dst, os.path.basename(src)) 

319 

320 link_errno = link(src, dst) 

321 if link_errno == errno.EEXIST: 

322 if os.stat(src).st_ino == os.stat(dst).st_ino: 

323 # dst is already a hard link to the correct file, so we don't need 

324 # to do anything else. If we try to link and rename the file 

325 # anyway, we get duplicate files - see http://bugs.python.org/issue21876 

326 return 

327 

328 import random 

329 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), ) 

330 try: 

331 link_or_copy(src, new_dst) 

332 except Exception: 

333 try: 

334 os.remove(new_dst) 

335 except OSError: 

336 pass 

337 raise 

338 os.rename(new_dst, dst) 

339 elif link_errno != 0: 

340 # Either link isn't supported, or the filesystem doesn't support 

341 # linking, or 'src' and 'dst' are on different filesystems. 

342 import shutil 

343 shutil.copy(src, dst) 

344 

345def ensure_dir_exists(path: str, mode: int=0o755): 

346 """ensure that a directory exists 

347 

348 If it doesn't exist, try to create it and protect against a race condition 

349 if another process is doing the same. 

350 

351 The default permissions are 755, which differ from os.makedirs default of 777. 

352 """ 

353 if not os.path.exists(path): 

354 try: 

355 os.makedirs(path, mode=mode) 

356 except OSError as e: 

357 if e.errno != errno.EEXIST: 

358 raise 

359 elif not os.path.isdir(path): 

360 raise OSError("%r exists but is not a directory" % path)