##// END OF EJS Templates
Merge pull request #13224 from jrabinow/xdg_dir_support_posix...
Matthias Bussonnier -
r27365:6d92ccfc merge
parent child Browse files
Show More
@@ -1,205 +1,201 b''
1 import errno
1 import errno
2 import os
2 import os
3 import shutil
3 import shutil
4 import sys
4 import sys
5 import tempfile
5 import tempfile
6 import warnings
6 import warnings
7 from unittest.mock import patch
7 from unittest.mock import patch
8
8
9 from testpath import modified_env, assert_isdir, assert_isfile
9 from testpath import modified_env, assert_isdir, assert_isfile
10
10
11 from IPython import paths
11 from IPython import paths
12 from IPython.testing.decorators import skip_win32
12 from IPython.testing.decorators import skip_win32
13 from IPython.utils.tempdir import TemporaryDirectory
13 from IPython.utils.tempdir import TemporaryDirectory
14
14
15 TMP_TEST_DIR = os.path.realpath(tempfile.mkdtemp())
15 TMP_TEST_DIR = os.path.realpath(tempfile.mkdtemp())
16 HOME_TEST_DIR = os.path.join(TMP_TEST_DIR, "home_test_dir")
16 HOME_TEST_DIR = os.path.join(TMP_TEST_DIR, "home_test_dir")
17 XDG_TEST_DIR = os.path.join(HOME_TEST_DIR, "xdg_test_dir")
17 XDG_TEST_DIR = os.path.join(HOME_TEST_DIR, "xdg_test_dir")
18 XDG_CACHE_DIR = os.path.join(HOME_TEST_DIR, "xdg_cache_dir")
18 XDG_CACHE_DIR = os.path.join(HOME_TEST_DIR, "xdg_cache_dir")
19 IP_TEST_DIR = os.path.join(HOME_TEST_DIR,'.ipython')
19 IP_TEST_DIR = os.path.join(HOME_TEST_DIR,'.ipython')
20
20
21 def setup_module():
21 def setup_module():
22 """Setup testenvironment for the module:
22 """Setup testenvironment for the module:
23
23
24 - Adds dummy home dir tree
24 - Adds dummy home dir tree
25 """
25 """
26 # Do not mask exceptions here. In particular, catching WindowsError is a
26 # Do not mask exceptions here. In particular, catching WindowsError is a
27 # problem because that exception is only defined on Windows...
27 # problem because that exception is only defined on Windows...
28 os.makedirs(IP_TEST_DIR)
28 os.makedirs(IP_TEST_DIR)
29 os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython'))
29 os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython'))
30 os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython'))
30 os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython'))
31
31
32
32
33 def teardown_module():
33 def teardown_module():
34 """Teardown testenvironment for the module:
34 """Teardown testenvironment for the module:
35
35
36 - Remove dummy home dir tree
36 - Remove dummy home dir tree
37 """
37 """
38 # Note: we remove the parent test dir, which is the root of all test
38 # Note: we remove the parent test dir, which is the root of all test
39 # subdirs we may have created. Use shutil instead of os.removedirs, so
39 # subdirs we may have created. Use shutil instead of os.removedirs, so
40 # that non-empty directories are all recursively removed.
40 # that non-empty directories are all recursively removed.
41 shutil.rmtree(TMP_TEST_DIR)
41 shutil.rmtree(TMP_TEST_DIR)
42
42
43 def patch_get_home_dir(dirpath):
43 def patch_get_home_dir(dirpath):
44 return patch.object(paths, 'get_home_dir', return_value=dirpath)
44 return patch.object(paths, 'get_home_dir', return_value=dirpath)
45
45
46
46
47 def test_get_ipython_dir_1():
47 def test_get_ipython_dir_1():
48 """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions."""
48 """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions."""
49 env_ipdir = os.path.join("someplace", ".ipython")
49 env_ipdir = os.path.join("someplace", ".ipython")
50 with patch.object(paths, '_writable_dir', return_value=True), \
50 with patch.object(paths, '_writable_dir', return_value=True), \
51 modified_env({'IPYTHONDIR': env_ipdir}):
51 modified_env({'IPYTHONDIR': env_ipdir}):
52 ipdir = paths.get_ipython_dir()
52 ipdir = paths.get_ipython_dir()
53
53
54 assert ipdir == env_ipdir
54 assert ipdir == env_ipdir
55
55
56 def test_get_ipython_dir_2():
56 def test_get_ipython_dir_2():
57 """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions."""
57 """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions."""
58 with patch_get_home_dir('someplace'), \
58 with patch_get_home_dir('someplace'), \
59 patch.object(paths, 'get_xdg_dir', return_value=None), \
59 patch.object(paths, 'get_xdg_dir', return_value=None), \
60 patch.object(paths, '_writable_dir', return_value=True), \
60 patch.object(paths, '_writable_dir', return_value=True), \
61 patch('os.name', "posix"), \
61 patch('os.name', "posix"), \
62 modified_env({'IPYTHON_DIR': None,
62 modified_env({'IPYTHON_DIR': None,
63 'IPYTHONDIR': None,
63 'IPYTHONDIR': None,
64 'XDG_CONFIG_HOME': None
64 'XDG_CONFIG_HOME': None
65 }):
65 }):
66 ipdir = paths.get_ipython_dir()
66 ipdir = paths.get_ipython_dir()
67
67
68 assert ipdir == os.path.join("someplace", ".ipython")
68 assert ipdir == os.path.join("someplace", ".ipython")
69
69
70 def test_get_ipython_dir_3():
70 def test_get_ipython_dir_3():
71 """test_get_ipython_dir_3, move XDG if defined, and .ipython doesn't exist."""
71 """test_get_ipython_dir_3, use XDG if defined and exists, and .ipython doesn't exist."""
72 tmphome = TemporaryDirectory()
72 tmphome = TemporaryDirectory()
73 try:
73 try:
74 with patch_get_home_dir(tmphome.name), \
74 with patch_get_home_dir(tmphome.name), \
75 patch('os.name', 'posix'), \
75 patch('os.name', 'posix'), \
76 modified_env({
76 modified_env({
77 'IPYTHON_DIR': None,
77 'IPYTHON_DIR': None,
78 'IPYTHONDIR': None,
78 'IPYTHONDIR': None,
79 'XDG_CONFIG_HOME': XDG_TEST_DIR,
79 'XDG_CONFIG_HOME': XDG_TEST_DIR,
80 }), warnings.catch_warnings(record=True) as w:
80 }), warnings.catch_warnings(record=True) as w:
81 ipdir = paths.get_ipython_dir()
81 ipdir = paths.get_ipython_dir()
82
82
83 assert ipdir == os.path.join(tmphome.name, ".ipython")
83 assert ipdir == os.path.join(tmphome.name, XDG_TEST_DIR, "ipython")
84 if sys.platform != 'darwin':
84 assert len(w) == 0
85 assert len(w) == 1
86 assert "Moving" in str(w[0])
87 finally:
85 finally:
88 tmphome.cleanup()
86 tmphome.cleanup()
89
87
90 def test_get_ipython_dir_4():
88 def test_get_ipython_dir_4():
91 """test_get_ipython_dir_4, warn if XDG and home both exist."""
89 """test_get_ipython_dir_4, warn if XDG and home both exist."""
92 with patch_get_home_dir(HOME_TEST_DIR), \
90 with patch_get_home_dir(HOME_TEST_DIR), \
93 patch('os.name', 'posix'):
91 patch('os.name', 'posix'):
94 try:
92 try:
95 os.mkdir(os.path.join(XDG_TEST_DIR, 'ipython'))
93 os.mkdir(os.path.join(XDG_TEST_DIR, 'ipython'))
96 except OSError as e:
94 except OSError as e:
97 if e.errno != errno.EEXIST:
95 if e.errno != errno.EEXIST:
98 raise
96 raise
99
97
100
98
101 with modified_env({
99 with modified_env({
102 'IPYTHON_DIR': None,
100 'IPYTHON_DIR': None,
103 'IPYTHONDIR': None,
101 'IPYTHONDIR': None,
104 'XDG_CONFIG_HOME': XDG_TEST_DIR,
102 'XDG_CONFIG_HOME': XDG_TEST_DIR,
105 }), warnings.catch_warnings(record=True) as w:
103 }), warnings.catch_warnings(record=True) as w:
106 ipdir = paths.get_ipython_dir()
104 ipdir = paths.get_ipython_dir()
107
105
108 assert ipdir == os.path.join(HOME_TEST_DIR, ".ipython")
106 assert len(w) == 1
109 if sys.platform != 'darwin':
107 assert "Ignoring" in str(w[0])
110 assert len(w) == 1
111 assert "Ignoring" in str(w[0])
112
108
113
109
114 def test_get_ipython_dir_5():
110 def test_get_ipython_dir_5():
115 """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist."""
111 """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist."""
116 with patch_get_home_dir(HOME_TEST_DIR), \
112 with patch_get_home_dir(HOME_TEST_DIR), \
117 patch('os.name', 'posix'):
113 patch('os.name', 'posix'):
118 try:
114 try:
119 os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython'))
115 os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython'))
120 except OSError as e:
116 except OSError as e:
121 if e.errno != errno.ENOENT:
117 if e.errno != errno.ENOENT:
122 raise
118 raise
123
119
124 with modified_env({
120 with modified_env({
125 'IPYTHON_DIR': None,
121 'IPYTHON_DIR': None,
126 'IPYTHONDIR': None,
122 'IPYTHONDIR': None,
127 'XDG_CONFIG_HOME': XDG_TEST_DIR,
123 'XDG_CONFIG_HOME': XDG_TEST_DIR,
128 }):
124 }):
129 ipdir = paths.get_ipython_dir()
125 ipdir = paths.get_ipython_dir()
130
126
131 assert ipdir == IP_TEST_DIR
127 assert ipdir == IP_TEST_DIR
132
128
133 def test_get_ipython_dir_6():
129 def test_get_ipython_dir_6():
134 """test_get_ipython_dir_6, use home over XDG if defined and neither exist."""
130 """test_get_ipython_dir_6, use home over XDG if defined and neither exist."""
135 xdg = os.path.join(HOME_TEST_DIR, 'somexdg')
131 xdg = os.path.join(HOME_TEST_DIR, 'somexdg')
136 os.mkdir(xdg)
132 os.mkdir(xdg)
137 shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython'))
133 shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython'))
138 print(paths._writable_dir)
134 print(paths._writable_dir)
139 with patch_get_home_dir(HOME_TEST_DIR), \
135 with patch_get_home_dir(HOME_TEST_DIR), \
140 patch.object(paths, 'get_xdg_dir', return_value=xdg), \
136 patch.object(paths, 'get_xdg_dir', return_value=xdg), \
141 patch('os.name', 'posix'), \
137 patch('os.name', 'posix'), \
142 modified_env({
138 modified_env({
143 'IPYTHON_DIR': None,
139 'IPYTHON_DIR': None,
144 'IPYTHONDIR': None,
140 'IPYTHONDIR': None,
145 'XDG_CONFIG_HOME': None,
141 'XDG_CONFIG_HOME': None,
146 }), warnings.catch_warnings(record=True) as w:
142 }), warnings.catch_warnings(record=True) as w:
147 ipdir = paths.get_ipython_dir()
143 ipdir = paths.get_ipython_dir()
148
144
149 assert ipdir == os.path.join(HOME_TEST_DIR, ".ipython")
145 assert ipdir == os.path.join(HOME_TEST_DIR, ".ipython")
150 assert len(w) == 0
146 assert len(w) == 0
151
147
152 def test_get_ipython_dir_7():
148 def test_get_ipython_dir_7():
153 """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR"""
149 """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR"""
154 home_dir = os.path.normpath(os.path.expanduser('~'))
150 home_dir = os.path.normpath(os.path.expanduser('~'))
155 with modified_env({'IPYTHONDIR': os.path.join('~', 'somewhere')}), \
151 with modified_env({'IPYTHONDIR': os.path.join('~', 'somewhere')}), \
156 patch.object(paths, '_writable_dir', return_value=True):
152 patch.object(paths, '_writable_dir', return_value=True):
157 ipdir = paths.get_ipython_dir()
153 ipdir = paths.get_ipython_dir()
158 assert ipdir == os.path.join(home_dir, "somewhere")
154 assert ipdir == os.path.join(home_dir, "somewhere")
159
155
160
156
161 @skip_win32
157 @skip_win32
162 def test_get_ipython_dir_8():
158 def test_get_ipython_dir_8():
163 """test_get_ipython_dir_8, test / home directory"""
159 """test_get_ipython_dir_8, test / home directory"""
164 if not os.access("/", os.W_OK):
160 if not os.access("/", os.W_OK):
165 # test only when HOME directory actually writable
161 # test only when HOME directory actually writable
166 return
162 return
167
163
168 with patch.object(paths, "_writable_dir", lambda path: bool(path)), patch.object(
164 with patch.object(paths, "_writable_dir", lambda path: bool(path)), patch.object(
169 paths, "get_xdg_dir", return_value=None
165 paths, "get_xdg_dir", return_value=None
170 ), modified_env(
166 ), modified_env(
171 {
167 {
172 "IPYTHON_DIR": None,
168 "IPYTHON_DIR": None,
173 "IPYTHONDIR": None,
169 "IPYTHONDIR": None,
174 "HOME": "/",
170 "HOME": "/",
175 }
171 }
176 ):
172 ):
177 assert paths.get_ipython_dir() == "/.ipython"
173 assert paths.get_ipython_dir() == "/.ipython"
178
174
179
175
180 def test_get_ipython_cache_dir():
176 def test_get_ipython_cache_dir():
181 with modified_env({'HOME': HOME_TEST_DIR}):
177 with modified_env({'HOME': HOME_TEST_DIR}):
182 if os.name == 'posix' and sys.platform != 'darwin':
178 if os.name == "posix":
183 # test default
179 # test default
184 os.makedirs(os.path.join(HOME_TEST_DIR, ".cache"))
180 os.makedirs(os.path.join(HOME_TEST_DIR, ".cache"))
185 with modified_env({'XDG_CACHE_HOME': None}):
181 with modified_env({'XDG_CACHE_HOME': None}):
186 ipdir = paths.get_ipython_cache_dir()
182 ipdir = paths.get_ipython_cache_dir()
187 assert os.path.join(HOME_TEST_DIR, ".cache", "ipython") == ipdir
183 assert os.path.join(HOME_TEST_DIR, ".cache", "ipython") == ipdir
188 assert_isdir(ipdir)
184 assert_isdir(ipdir)
189
185
190 # test env override
186 # test env override
191 with modified_env({"XDG_CACHE_HOME": XDG_CACHE_DIR}):
187 with modified_env({"XDG_CACHE_HOME": XDG_CACHE_DIR}):
192 ipdir = paths.get_ipython_cache_dir()
188 ipdir = paths.get_ipython_cache_dir()
193 assert_isdir(ipdir)
189 assert_isdir(ipdir)
194 assert ipdir == os.path.join(XDG_CACHE_DIR, "ipython")
190 assert ipdir == os.path.join(XDG_CACHE_DIR, "ipython")
195 else:
191 else:
196 assert paths.get_ipython_cache_dir() == paths.get_ipython_dir()
192 assert paths.get_ipython_cache_dir() == paths.get_ipython_dir()
197
193
198 def test_get_ipython_package_dir():
194 def test_get_ipython_package_dir():
199 ipdir = paths.get_ipython_package_dir()
195 ipdir = paths.get_ipython_package_dir()
200 assert_isdir(ipdir)
196 assert_isdir(ipdir)
201
197
202
198
203 def test_get_ipython_module_path():
199 def test_get_ipython_module_path():
204 ipapp_path = paths.get_ipython_module_path('IPython.terminal.ipapp')
200 ipapp_path = paths.get_ipython_module_path('IPython.terminal.ipapp')
205 assert_isfile(ipapp_path)
201 assert_isfile(ipapp_path)
@@ -1,127 +1,126 b''
1 """Find files and directories which IPython uses.
1 """Find files and directories which IPython uses.
2 """
2 """
3 import os.path
3 import os.path
4 import shutil
4 import shutil
5 import tempfile
5 import tempfile
6 from warnings import warn
6 from warnings import warn
7
7
8 import IPython
8 import IPython
9 from IPython.utils.importstring import import_item
9 from IPython.utils.importstring import import_item
10 from IPython.utils.path import (
10 from IPython.utils.path import (
11 get_home_dir,
11 get_home_dir,
12 get_xdg_dir,
12 get_xdg_dir,
13 get_xdg_cache_dir,
13 get_xdg_cache_dir,
14 compress_user,
14 compress_user,
15 _writable_dir,
15 _writable_dir,
16 ensure_dir_exists,
16 ensure_dir_exists,
17 )
17 )
18
18
19
19
20 def get_ipython_dir() -> str:
20 def get_ipython_dir() -> str:
21 """Get the IPython directory for this platform and user.
21 """Get the IPython directory for this platform and user.
22
22
23 This uses the logic in `get_home_dir` to find the home directory
23 This uses the logic in `get_home_dir` to find the home directory
24 and then adds .ipython to the end of the path.
24 and then adds .ipython to the end of the path.
25 """
25 """
26
26
27 env = os.environ
27 env = os.environ
28 pjoin = os.path.join
28 pjoin = os.path.join
29
29
30
30
31 ipdir_def = '.ipython'
31 ipdir_def = '.ipython'
32
32
33 home_dir = get_home_dir()
33 home_dir = get_home_dir()
34 xdg_dir = get_xdg_dir()
34 xdg_dir = get_xdg_dir()
35
35
36 if 'IPYTHON_DIR' in env:
36 if 'IPYTHON_DIR' in env:
37 warn('The environment variable IPYTHON_DIR is deprecated since IPython 3.0. '
37 warn('The environment variable IPYTHON_DIR is deprecated since IPython 3.0. '
38 'Please use IPYTHONDIR instead.', DeprecationWarning)
38 'Please use IPYTHONDIR instead.', DeprecationWarning)
39 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
39 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
40 if ipdir is None:
40 if ipdir is None:
41 # not set explicitly, use ~/.ipython
41 # not set explicitly, use ~/.ipython
42 ipdir = pjoin(home_dir, ipdir_def)
42 ipdir = pjoin(home_dir, ipdir_def)
43 if xdg_dir:
43 if xdg_dir:
44 # Several IPython versions (up to 1.x) defaulted to .config/ipython
44 # Several IPython versions (up to 1.x) defaulted to .config/ipython
45 # on Linux. We have decided to go back to using .ipython everywhere
45 # on Linux. We have decided to go back to using .ipython everywhere
46 xdg_ipdir = pjoin(xdg_dir, 'ipython')
46 xdg_ipdir = pjoin(xdg_dir, 'ipython')
47
47
48 if _writable_dir(xdg_ipdir):
48 if _writable_dir(xdg_ipdir):
49 cu = compress_user
49 cu = compress_user
50 if os.path.exists(ipdir):
50 if os.path.exists(ipdir):
51 warn(('Ignoring {0} in favour of {1}. Remove {0} to '
51 warn(('Ignoring {0} in favour of {1}. Remove {0} to '
52 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
52 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
53 elif os.path.islink(xdg_ipdir):
53 elif os.path.islink(xdg_ipdir):
54 warn(('{0} is deprecated. Move link to {1} to '
54 warn(('{0} is deprecated. Move link to {1} to '
55 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
55 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
56 else:
56 else:
57 warn('Moving {0} to {1}'.format(cu(xdg_ipdir), cu(ipdir)))
57 ipdir = xdg_ipdir
58 shutil.move(xdg_ipdir, ipdir)
59
58
60 ipdir = os.path.normpath(os.path.expanduser(ipdir))
59 ipdir = os.path.normpath(os.path.expanduser(ipdir))
61
60
62 if os.path.exists(ipdir) and not _writable_dir(ipdir):
61 if os.path.exists(ipdir) and not _writable_dir(ipdir):
63 # ipdir exists, but is not writable
62 # ipdir exists, but is not writable
64 warn("IPython dir '{0}' is not a writable location,"
63 warn("IPython dir '{0}' is not a writable location,"
65 " using a temp directory.".format(ipdir))
64 " using a temp directory.".format(ipdir))
66 ipdir = tempfile.mkdtemp()
65 ipdir = tempfile.mkdtemp()
67 elif not os.path.exists(ipdir):
66 elif not os.path.exists(ipdir):
68 parent = os.path.dirname(ipdir)
67 parent = os.path.dirname(ipdir)
69 if not _writable_dir(parent):
68 if not _writable_dir(parent):
70 # ipdir does not exist and parent isn't writable
69 # ipdir does not exist and parent isn't writable
71 warn("IPython parent '{0}' is not a writable location,"
70 warn("IPython parent '{0}' is not a writable location,"
72 " using a temp directory.".format(parent))
71 " using a temp directory.".format(parent))
73 ipdir = tempfile.mkdtemp()
72 ipdir = tempfile.mkdtemp()
74 else:
73 else:
75 os.makedirs(ipdir, exist_ok=True)
74 os.makedirs(ipdir, exist_ok=True)
76 assert isinstance(ipdir, str), "all path manipulation should be str(unicode), but are not."
75 assert isinstance(ipdir, str), "all path manipulation should be str(unicode), but are not."
77 return ipdir
76 return ipdir
78
77
79
78
80 def get_ipython_cache_dir() -> str:
79 def get_ipython_cache_dir() -> str:
81 """Get the cache directory it is created if it does not exist."""
80 """Get the cache directory it is created if it does not exist."""
82 xdgdir = get_xdg_cache_dir()
81 xdgdir = get_xdg_cache_dir()
83 if xdgdir is None:
82 if xdgdir is None:
84 return get_ipython_dir()
83 return get_ipython_dir()
85 ipdir = os.path.join(xdgdir, "ipython")
84 ipdir = os.path.join(xdgdir, "ipython")
86 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
85 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
87 ensure_dir_exists(ipdir)
86 ensure_dir_exists(ipdir)
88 elif not _writable_dir(xdgdir):
87 elif not _writable_dir(xdgdir):
89 return get_ipython_dir()
88 return get_ipython_dir()
90
89
91 return ipdir
90 return ipdir
92
91
93
92
94 def get_ipython_package_dir() -> str:
93 def get_ipython_package_dir() -> str:
95 """Get the base directory where IPython itself is installed."""
94 """Get the base directory where IPython itself is installed."""
96 ipdir = os.path.dirname(IPython.__file__)
95 ipdir = os.path.dirname(IPython.__file__)
97 assert isinstance(ipdir, str)
96 assert isinstance(ipdir, str)
98 return ipdir
97 return ipdir
99
98
100
99
101 def get_ipython_module_path(module_str):
100 def get_ipython_module_path(module_str):
102 """Find the path to an IPython module in this version of IPython.
101 """Find the path to an IPython module in this version of IPython.
103
102
104 This will always find the version of the module that is in this importable
103 This will always find the version of the module that is in this importable
105 IPython package. This will always return the path to the ``.py``
104 IPython package. This will always return the path to the ``.py``
106 version of the module.
105 version of the module.
107 """
106 """
108 if module_str == 'IPython':
107 if module_str == 'IPython':
109 return os.path.join(get_ipython_package_dir(), '__init__.py')
108 return os.path.join(get_ipython_package_dir(), '__init__.py')
110 mod = import_item(module_str)
109 mod = import_item(module_str)
111 the_path = mod.__file__.replace('.pyc', '.py')
110 the_path = mod.__file__.replace('.pyc', '.py')
112 the_path = the_path.replace('.pyo', '.py')
111 the_path = the_path.replace('.pyo', '.py')
113 return the_path
112 return the_path
114
113
115
114
116 def locate_profile(profile='default'):
115 def locate_profile(profile='default'):
117 """Find the path to the folder associated with a given profile.
116 """Find the path to the folder associated with a given profile.
118
117
119 I.e. find $IPYTHONDIR/profile_whatever.
118 I.e. find $IPYTHONDIR/profile_whatever.
120 """
119 """
121 from IPython.core.profiledir import ProfileDir, ProfileDirError
120 from IPython.core.profiledir import ProfileDir, ProfileDirError
122 try:
121 try:
123 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
122 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
124 except ProfileDirError as e:
123 except ProfileDirError as e:
125 # IOError makes more sense when people are expecting a path
124 # IOError makes more sense when people are expecting a path
126 raise IOError("Couldn't find profile %r" % profile) from e
125 raise IOError("Couldn't find profile %r" % profile) from e
127 return pd.location
126 return pd.location
@@ -1,392 +1,392 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for path handling.
3 Utilities for path handling.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 import os
9 import os
10 import sys
10 import sys
11 import errno
11 import errno
12 import shutil
12 import shutil
13 import random
13 import random
14 import glob
14 import glob
15 from warnings import warn
15 from warnings import warn
16
16
17 from IPython.utils.process import system
17 from IPython.utils.process import system
18 from IPython.utils.decorators import undoc
18 from IPython.utils.decorators import undoc
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Code
21 # Code
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 fs_encoding = sys.getfilesystemencoding()
23 fs_encoding = sys.getfilesystemencoding()
24
24
25 def _writable_dir(path):
25 def _writable_dir(path):
26 """Whether `path` is a directory, to which the user has write access."""
26 """Whether `path` is a directory, to which the user has write access."""
27 return os.path.isdir(path) and os.access(path, os.W_OK)
27 return os.path.isdir(path) and os.access(path, os.W_OK)
28
28
29 if sys.platform == 'win32':
29 if sys.platform == 'win32':
30 def _get_long_path_name(path):
30 def _get_long_path_name(path):
31 """Get a long path name (expand ~) on Windows using ctypes.
31 """Get a long path name (expand ~) on Windows using ctypes.
32
32
33 Examples
33 Examples
34 --------
34 --------
35
35
36 >>> get_long_path_name('c:\\\\docume~1')
36 >>> get_long_path_name('c:\\\\docume~1')
37 'c:\\\\Documents and Settings'
37 'c:\\\\Documents and Settings'
38
38
39 """
39 """
40 try:
40 try:
41 import ctypes
41 import ctypes
42 except ImportError as e:
42 except ImportError as e:
43 raise ImportError('you need to have ctypes installed for this to work') from e
43 raise ImportError('you need to have ctypes installed for this to work') from e
44 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
44 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
45 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
45 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
46 ctypes.c_uint ]
46 ctypes.c_uint ]
47
47
48 buf = ctypes.create_unicode_buffer(260)
48 buf = ctypes.create_unicode_buffer(260)
49 rv = _GetLongPathName(path, buf, 260)
49 rv = _GetLongPathName(path, buf, 260)
50 if rv == 0 or rv > 260:
50 if rv == 0 or rv > 260:
51 return path
51 return path
52 else:
52 else:
53 return buf.value
53 return buf.value
54 else:
54 else:
55 def _get_long_path_name(path):
55 def _get_long_path_name(path):
56 """Dummy no-op."""
56 """Dummy no-op."""
57 return path
57 return path
58
58
59
59
60
60
61 def get_long_path_name(path):
61 def get_long_path_name(path):
62 """Expand a path into its long form.
62 """Expand a path into its long form.
63
63
64 On Windows this expands any ~ in the paths. On other platforms, it is
64 On Windows this expands any ~ in the paths. On other platforms, it is
65 a null operation.
65 a null operation.
66 """
66 """
67 return _get_long_path_name(path)
67 return _get_long_path_name(path)
68
68
69
69
70 def compress_user(path):
70 def compress_user(path):
71 """Reverse of :func:`os.path.expanduser`
71 """Reverse of :func:`os.path.expanduser`
72 """
72 """
73 home = os.path.expanduser('~')
73 home = os.path.expanduser('~')
74 if path.startswith(home):
74 if path.startswith(home):
75 path = "~" + path[len(home):]
75 path = "~" + path[len(home):]
76 return path
76 return path
77
77
78 def get_py_filename(name):
78 def get_py_filename(name):
79 """Return a valid python filename in the current directory.
79 """Return a valid python filename in the current directory.
80
80
81 If the given name is not a file, it adds '.py' and searches again.
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.
82 Raises IOError with an informative message if the file isn't found.
83 """
83 """
84
84
85 name = os.path.expanduser(name)
85 name = os.path.expanduser(name)
86 if not os.path.isfile(name) and not name.endswith('.py'):
86 if not os.path.isfile(name) and not name.endswith('.py'):
87 name += '.py'
87 name += '.py'
88 if os.path.isfile(name):
88 if os.path.isfile(name):
89 return name
89 return name
90 else:
90 else:
91 raise IOError('File `%r` not found.' % name)
91 raise IOError('File `%r` not found.' % name)
92
92
93
93
94 def filefind(filename: str, path_dirs=None) -> str:
94 def filefind(filename: str, path_dirs=None) -> str:
95 """Find a file by looking through a sequence of paths.
95 """Find a file by looking through a sequence of paths.
96
96
97 This iterates through a sequence of paths looking for a file and returns
97 This iterates through a sequence of paths looking for a file and returns
98 the full, absolute path of the first occurrence of the file. If no set of
98 the full, absolute path of the first occurrence of the file. If no set of
99 path dirs is given, the filename is tested as is, after running through
99 path dirs is given, the filename is tested as is, after running through
100 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
100 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
101
101
102 filefind('myfile.txt')
102 filefind('myfile.txt')
103
103
104 will find the file in the current working dir, but::
104 will find the file in the current working dir, but::
105
105
106 filefind('~/myfile.txt')
106 filefind('~/myfile.txt')
107
107
108 Will find the file in the users home directory. This function does not
108 Will find the file in the users home directory. This function does not
109 automatically try any paths, such as the cwd or the user's home directory.
109 automatically try any paths, such as the cwd or the user's home directory.
110
110
111 Parameters
111 Parameters
112 ----------
112 ----------
113 filename : str
113 filename : str
114 The filename to look for.
114 The filename to look for.
115 path_dirs : str, None or sequence of str
115 path_dirs : str, None or sequence of str
116 The sequence of paths to look for the file in. If None, the filename
116 The sequence of paths to look for the file in. If None, the filename
117 need to be absolute or be in the cwd. If a string, the string is
117 need to be absolute or be in the cwd. If a string, the string is
118 put into a sequence and the searched. If a sequence, walk through
118 put into a sequence and the searched. If a sequence, walk through
119 each element and join with ``filename``, calling :func:`expandvars`
119 each element and join with ``filename``, calling :func:`expandvars`
120 and :func:`expanduser` before testing for existence.
120 and :func:`expanduser` before testing for existence.
121
121
122 Returns
122 Returns
123 -------
123 -------
124 path : str
124 path : str
125 returns absolute path to file.
125 returns absolute path to file.
126
126
127 Raises
127 Raises
128 ------
128 ------
129 IOError
129 IOError
130 """
130 """
131
131
132 # If paths are quoted, abspath gets confused, strip them...
132 # If paths are quoted, abspath gets confused, strip them...
133 filename = filename.strip('"').strip("'")
133 filename = filename.strip('"').strip("'")
134 # If the input is an absolute path, just check it exists
134 # If the input is an absolute path, just check it exists
135 if os.path.isabs(filename) and os.path.isfile(filename):
135 if os.path.isabs(filename) and os.path.isfile(filename):
136 return filename
136 return filename
137
137
138 if path_dirs is None:
138 if path_dirs is None:
139 path_dirs = ("",)
139 path_dirs = ("",)
140 elif isinstance(path_dirs, str):
140 elif isinstance(path_dirs, str):
141 path_dirs = (path_dirs,)
141 path_dirs = (path_dirs,)
142
142
143 for path in path_dirs:
143 for path in path_dirs:
144 if path == '.': path = os.getcwd()
144 if path == '.': path = os.getcwd()
145 testname = expand_path(os.path.join(path, filename))
145 testname = expand_path(os.path.join(path, filename))
146 if os.path.isfile(testname):
146 if os.path.isfile(testname):
147 return os.path.abspath(testname)
147 return os.path.abspath(testname)
148
148
149 raise IOError("File %r does not exist in any of the search paths: %r" %
149 raise IOError("File %r does not exist in any of the search paths: %r" %
150 (filename, path_dirs) )
150 (filename, path_dirs) )
151
151
152
152
153 class HomeDirError(Exception):
153 class HomeDirError(Exception):
154 pass
154 pass
155
155
156
156
157 def get_home_dir(require_writable=False) -> str:
157 def get_home_dir(require_writable=False) -> str:
158 """Return the 'home' directory, as a unicode string.
158 """Return the 'home' directory, as a unicode string.
159
159
160 Uses os.path.expanduser('~'), and checks for writability.
160 Uses os.path.expanduser('~'), and checks for writability.
161
161
162 See stdlib docs for how this is determined.
162 See stdlib docs for how this is determined.
163 For Python <3.8, $HOME is first priority on *ALL* platforms.
163 For Python <3.8, $HOME is first priority on *ALL* platforms.
164 For Python >=3.8 on Windows, %HOME% is no longer considered.
164 For Python >=3.8 on Windows, %HOME% is no longer considered.
165
165
166 Parameters
166 Parameters
167 ----------
167 ----------
168 require_writable : bool [default: False]
168 require_writable : bool [default: False]
169 if True:
169 if True:
170 guarantees the return value is a writable directory, otherwise
170 guarantees the return value is a writable directory, otherwise
171 raises HomeDirError
171 raises HomeDirError
172 if False:
172 if False:
173 The path is resolved, but it is not guaranteed to exist or be writable.
173 The path is resolved, but it is not guaranteed to exist or be writable.
174 """
174 """
175
175
176 homedir = os.path.expanduser('~')
176 homedir = os.path.expanduser('~')
177 # Next line will make things work even when /home/ is a symlink to
177 # Next line will make things work even when /home/ is a symlink to
178 # /usr/home as it is on FreeBSD, for example
178 # /usr/home as it is on FreeBSD, for example
179 homedir = os.path.realpath(homedir)
179 homedir = os.path.realpath(homedir)
180
180
181 if not _writable_dir(homedir) and os.name == 'nt':
181 if not _writable_dir(homedir) and os.name == 'nt':
182 # expanduser failed, use the registry to get the 'My Documents' folder.
182 # expanduser failed, use the registry to get the 'My Documents' folder.
183 try:
183 try:
184 import winreg as wreg
184 import winreg as wreg
185 with wreg.OpenKey(
185 with wreg.OpenKey(
186 wreg.HKEY_CURRENT_USER,
186 wreg.HKEY_CURRENT_USER,
187 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
187 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
188 ) as key:
188 ) as key:
189 homedir = wreg.QueryValueEx(key,'Personal')[0]
189 homedir = wreg.QueryValueEx(key,'Personal')[0]
190 except:
190 except:
191 pass
191 pass
192
192
193 if (not require_writable) or _writable_dir(homedir):
193 if (not require_writable) or _writable_dir(homedir):
194 assert isinstance(homedir, str), "Homedir should be unicode not bytes"
194 assert isinstance(homedir, str), "Homedir should be unicode not bytes"
195 return homedir
195 return homedir
196 else:
196 else:
197 raise HomeDirError('%s is not a writable dir, '
197 raise HomeDirError('%s is not a writable dir, '
198 'set $HOME environment variable to override' % homedir)
198 'set $HOME environment variable to override' % homedir)
199
199
200 def get_xdg_dir():
200 def get_xdg_dir():
201 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
201 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
202
202
203 This is only for non-OS X posix (Linux,Unix,etc.) systems.
203 This is only for non-OS X posix (Linux,Unix,etc.) systems.
204 """
204 """
205
205
206 env = os.environ
206 env = os.environ
207
207
208 if os.name == 'posix' and sys.platform != 'darwin':
208 if os.name == "posix":
209 # Linux, Unix, AIX, etc.
209 # Linux, Unix, AIX, etc.
210 # use ~/.config if empty OR not set
210 # use ~/.config if empty OR not set
211 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
211 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
212 if xdg and _writable_dir(xdg):
212 if xdg and _writable_dir(xdg):
213 assert isinstance(xdg, str)
213 assert isinstance(xdg, str)
214 return xdg
214 return xdg
215
215
216 return None
216 return None
217
217
218
218
219 def get_xdg_cache_dir():
219 def get_xdg_cache_dir():
220 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
220 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
221
221
222 This is only for non-OS X posix (Linux,Unix,etc.) systems.
222 This is only for non-OS X posix (Linux,Unix,etc.) systems.
223 """
223 """
224
224
225 env = os.environ
225 env = os.environ
226
226
227 if os.name == 'posix' and sys.platform != 'darwin':
227 if os.name == "posix":
228 # Linux, Unix, AIX, etc.
228 # Linux, Unix, AIX, etc.
229 # use ~/.cache if empty OR not set
229 # use ~/.cache if empty OR not set
230 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
230 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
231 if xdg and _writable_dir(xdg):
231 if xdg and _writable_dir(xdg):
232 assert isinstance(xdg, str)
232 assert isinstance(xdg, str)
233 return xdg
233 return xdg
234
234
235 return None
235 return None
236
236
237
237
238 def expand_path(s):
238 def expand_path(s):
239 """Expand $VARS and ~names in a string, like a shell
239 """Expand $VARS and ~names in a string, like a shell
240
240
241 :Examples:
241 :Examples:
242
242
243 In [2]: os.environ['FOO']='test'
243 In [2]: os.environ['FOO']='test'
244
244
245 In [3]: expand_path('variable FOO is $FOO')
245 In [3]: expand_path('variable FOO is $FOO')
246 Out[3]: 'variable FOO is test'
246 Out[3]: 'variable FOO is test'
247 """
247 """
248 # This is a pretty subtle hack. When expand user is given a UNC path
248 # This is a pretty subtle hack. When expand user is given a UNC path
249 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
249 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
250 # the $ to get (\\server\share\%username%). I think it considered $
250 # the $ to get (\\server\share\%username%). I think it considered $
251 # alone an empty var. But, we need the $ to remains there (it indicates
251 # alone an empty var. But, we need the $ to remains there (it indicates
252 # a hidden share).
252 # a hidden share).
253 if os.name=='nt':
253 if os.name=='nt':
254 s = s.replace('$\\', 'IPYTHON_TEMP')
254 s = s.replace('$\\', 'IPYTHON_TEMP')
255 s = os.path.expandvars(os.path.expanduser(s))
255 s = os.path.expandvars(os.path.expanduser(s))
256 if os.name=='nt':
256 if os.name=='nt':
257 s = s.replace('IPYTHON_TEMP', '$\\')
257 s = s.replace('IPYTHON_TEMP', '$\\')
258 return s
258 return s
259
259
260
260
261 def unescape_glob(string):
261 def unescape_glob(string):
262 """Unescape glob pattern in `string`."""
262 """Unescape glob pattern in `string`."""
263 def unescape(s):
263 def unescape(s):
264 for pattern in '*[]!?':
264 for pattern in '*[]!?':
265 s = s.replace(r'\{0}'.format(pattern), pattern)
265 s = s.replace(r'\{0}'.format(pattern), pattern)
266 return s
266 return s
267 return '\\'.join(map(unescape, string.split('\\\\')))
267 return '\\'.join(map(unescape, string.split('\\\\')))
268
268
269
269
270 def shellglob(args):
270 def shellglob(args):
271 """
271 """
272 Do glob expansion for each element in `args` and return a flattened list.
272 Do glob expansion for each element in `args` and return a flattened list.
273
273
274 Unmatched glob pattern will remain as-is in the returned list.
274 Unmatched glob pattern will remain as-is in the returned list.
275
275
276 """
276 """
277 expanded = []
277 expanded = []
278 # Do not unescape backslash in Windows as it is interpreted as
278 # Do not unescape backslash in Windows as it is interpreted as
279 # path separator:
279 # path separator:
280 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
280 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
281 for a in args:
281 for a in args:
282 expanded.extend(glob.glob(a) or [unescape(a)])
282 expanded.extend(glob.glob(a) or [unescape(a)])
283 return expanded
283 return expanded
284
284
285
285
286 def target_outdated(target,deps):
286 def target_outdated(target,deps):
287 """Determine whether a target is out of date.
287 """Determine whether a target is out of date.
288
288
289 target_outdated(target,deps) -> 1/0
289 target_outdated(target,deps) -> 1/0
290
290
291 deps: list of filenames which MUST exist.
291 deps: list of filenames which MUST exist.
292 target: single filename which may or may not exist.
292 target: single filename which may or may not exist.
293
293
294 If target doesn't exist or is older than any file listed in deps, return
294 If target doesn't exist or is older than any file listed in deps, return
295 true, otherwise return false.
295 true, otherwise return false.
296 """
296 """
297 try:
297 try:
298 target_time = os.path.getmtime(target)
298 target_time = os.path.getmtime(target)
299 except os.error:
299 except os.error:
300 return 1
300 return 1
301 for dep in deps:
301 for dep in deps:
302 dep_time = os.path.getmtime(dep)
302 dep_time = os.path.getmtime(dep)
303 if dep_time > target_time:
303 if dep_time > target_time:
304 #print "For target",target,"Dep failed:",dep # dbg
304 #print "For target",target,"Dep failed:",dep # dbg
305 #print "times (dep,tar):",dep_time,target_time # dbg
305 #print "times (dep,tar):",dep_time,target_time # dbg
306 return 1
306 return 1
307 return 0
307 return 0
308
308
309
309
310 def target_update(target,deps,cmd):
310 def target_update(target,deps,cmd):
311 """Update a target with a given command given a list of dependencies.
311 """Update a target with a given command given a list of dependencies.
312
312
313 target_update(target,deps,cmd) -> runs cmd if target is outdated.
313 target_update(target,deps,cmd) -> runs cmd if target is outdated.
314
314
315 This is just a wrapper around target_outdated() which calls the given
315 This is just a wrapper around target_outdated() which calls the given
316 command if target is outdated."""
316 command if target is outdated."""
317
317
318 if target_outdated(target,deps):
318 if target_outdated(target,deps):
319 system(cmd)
319 system(cmd)
320
320
321
321
322 ENOLINK = 1998
322 ENOLINK = 1998
323
323
324 def link(src, dst):
324 def link(src, dst):
325 """Hard links ``src`` to ``dst``, returning 0 or errno.
325 """Hard links ``src`` to ``dst``, returning 0 or errno.
326
326
327 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
327 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
328 supported by the operating system.
328 supported by the operating system.
329 """
329 """
330
330
331 if not hasattr(os, "link"):
331 if not hasattr(os, "link"):
332 return ENOLINK
332 return ENOLINK
333 link_errno = 0
333 link_errno = 0
334 try:
334 try:
335 os.link(src, dst)
335 os.link(src, dst)
336 except OSError as e:
336 except OSError as e:
337 link_errno = e.errno
337 link_errno = e.errno
338 return link_errno
338 return link_errno
339
339
340
340
341 def link_or_copy(src, dst):
341 def link_or_copy(src, dst):
342 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
342 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
343
343
344 Attempts to maintain the semantics of ``shutil.copy``.
344 Attempts to maintain the semantics of ``shutil.copy``.
345
345
346 Because ``os.link`` does not overwrite files, a unique temporary file
346 Because ``os.link`` does not overwrite files, a unique temporary file
347 will be used if the target already exists, then that file will be moved
347 will be used if the target already exists, then that file will be moved
348 into place.
348 into place.
349 """
349 """
350
350
351 if os.path.isdir(dst):
351 if os.path.isdir(dst):
352 dst = os.path.join(dst, os.path.basename(src))
352 dst = os.path.join(dst, os.path.basename(src))
353
353
354 link_errno = link(src, dst)
354 link_errno = link(src, dst)
355 if link_errno == errno.EEXIST:
355 if link_errno == errno.EEXIST:
356 if os.stat(src).st_ino == os.stat(dst).st_ino:
356 if os.stat(src).st_ino == os.stat(dst).st_ino:
357 # dst is already a hard link to the correct file, so we don't need
357 # dst is already a hard link to the correct file, so we don't need
358 # to do anything else. If we try to link and rename the file
358 # to do anything else. If we try to link and rename the file
359 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
359 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
360 return
360 return
361
361
362 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
362 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
363 try:
363 try:
364 link_or_copy(src, new_dst)
364 link_or_copy(src, new_dst)
365 except:
365 except:
366 try:
366 try:
367 os.remove(new_dst)
367 os.remove(new_dst)
368 except OSError:
368 except OSError:
369 pass
369 pass
370 raise
370 raise
371 os.rename(new_dst, dst)
371 os.rename(new_dst, dst)
372 elif link_errno != 0:
372 elif link_errno != 0:
373 # Either link isn't supported, or the filesystem doesn't support
373 # Either link isn't supported, or the filesystem doesn't support
374 # linking, or 'src' and 'dst' are on different filesystems.
374 # linking, or 'src' and 'dst' are on different filesystems.
375 shutil.copy(src, dst)
375 shutil.copy(src, dst)
376
376
377 def ensure_dir_exists(path, mode=0o755):
377 def ensure_dir_exists(path, mode=0o755):
378 """ensure that a directory exists
378 """ensure that a directory exists
379
379
380 If it doesn't exist, try to create it and protect against a race condition
380 If it doesn't exist, try to create it and protect against a race condition
381 if another process is doing the same.
381 if another process is doing the same.
382
382
383 The default permissions are 755, which differ from os.makedirs default of 777.
383 The default permissions are 755, which differ from os.makedirs default of 777.
384 """
384 """
385 if not os.path.exists(path):
385 if not os.path.exists(path):
386 try:
386 try:
387 os.makedirs(path, mode=mode)
387 os.makedirs(path, mode=mode)
388 except OSError as e:
388 except OSError as e:
389 if e.errno != errno.EEXIST:
389 if e.errno != errno.EEXIST:
390 raise
390 raise
391 elif not os.path.isdir(path):
391 elif not os.path.isdir(path):
392 raise IOError("%r exists but is not a directory" % path)
392 raise IOError("%r exists but is not a directory" % path)
@@ -1,504 +1,504 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.path.py"""
2 """Tests for IPython.utils.path.py"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import os
7 import os
8 import shutil
8 import shutil
9 import sys
9 import sys
10 import tempfile
10 import tempfile
11 import unittest
11 import unittest
12 from contextlib import contextmanager
12 from contextlib import contextmanager
13 from unittest.mock import patch
13 from unittest.mock import patch
14 from os.path import join, abspath
14 from os.path import join, abspath
15 from importlib import reload
15 from importlib import reload
16
16
17 import pytest
17 import pytest
18
18
19 import IPython
19 import IPython
20 from IPython import paths
20 from IPython import paths
21 from IPython.testing import decorators as dec
21 from IPython.testing import decorators as dec
22 from IPython.testing.decorators import (
22 from IPython.testing.decorators import (
23 skip_if_not_win32,
23 skip_if_not_win32,
24 skip_win32,
24 skip_win32,
25 onlyif_unicode_paths,
25 onlyif_unicode_paths,
26 )
26 )
27 from IPython.testing.tools import make_tempfile
27 from IPython.testing.tools import make_tempfile
28 from IPython.utils import path
28 from IPython.utils import path
29 from IPython.utils.tempdir import TemporaryDirectory
29 from IPython.utils.tempdir import TemporaryDirectory
30
30
31
31
32 # Platform-dependent imports
32 # Platform-dependent imports
33 try:
33 try:
34 import winreg as wreg
34 import winreg as wreg
35 except ImportError:
35 except ImportError:
36 #Fake _winreg module on non-windows platforms
36 #Fake _winreg module on non-windows platforms
37 import types
37 import types
38 wr_name = "winreg"
38 wr_name = "winreg"
39 sys.modules[wr_name] = types.ModuleType(wr_name)
39 sys.modules[wr_name] = types.ModuleType(wr_name)
40 try:
40 try:
41 import winreg as wreg
41 import winreg as wreg
42 except ImportError:
42 except ImportError:
43 import _winreg as wreg
43 import _winreg as wreg
44 #Add entries that needs to be stubbed by the testing code
44 #Add entries that needs to be stubbed by the testing code
45 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
45 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
46
46
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48 # Globals
48 # Globals
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50 env = os.environ
50 env = os.environ
51 TMP_TEST_DIR = tempfile.mkdtemp()
51 TMP_TEST_DIR = tempfile.mkdtemp()
52 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
52 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
53 #
53 #
54 # Setup/teardown functions/decorators
54 # Setup/teardown functions/decorators
55 #
55 #
56
56
57 def setup_module():
57 def setup_module():
58 """Setup testenvironment for the module:
58 """Setup testenvironment for the module:
59
59
60 - Adds dummy home dir tree
60 - Adds dummy home dir tree
61 """
61 """
62 # Do not mask exceptions here. In particular, catching WindowsError is a
62 # Do not mask exceptions here. In particular, catching WindowsError is a
63 # problem because that exception is only defined on Windows...
63 # problem because that exception is only defined on Windows...
64 os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython'))
64 os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython'))
65
65
66
66
67 def teardown_module():
67 def teardown_module():
68 """Teardown testenvironment for the module:
68 """Teardown testenvironment for the module:
69
69
70 - Remove dummy home dir tree
70 - Remove dummy home dir tree
71 """
71 """
72 # Note: we remove the parent test dir, which is the root of all test
72 # Note: we remove the parent test dir, which is the root of all test
73 # subdirs we may have created. Use shutil instead of os.removedirs, so
73 # subdirs we may have created. Use shutil instead of os.removedirs, so
74 # that non-empty directories are all recursively removed.
74 # that non-empty directories are all recursively removed.
75 shutil.rmtree(TMP_TEST_DIR)
75 shutil.rmtree(TMP_TEST_DIR)
76
76
77
77
78 def setup_environment():
78 def setup_environment():
79 """Setup testenvironment for some functions that are tested
79 """Setup testenvironment for some functions that are tested
80 in this module. In particular this functions stores attributes
80 in this module. In particular this functions stores attributes
81 and other things that we need to stub in some test functions.
81 and other things that we need to stub in some test functions.
82 This needs to be done on a function level and not module level because
82 This needs to be done on a function level and not module level because
83 each testfunction needs a pristine environment.
83 each testfunction needs a pristine environment.
84 """
84 """
85 global oldstuff, platformstuff
85 global oldstuff, platformstuff
86 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
86 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
87
87
88 def teardown_environment():
88 def teardown_environment():
89 """Restore things that were remembered by the setup_environment function
89 """Restore things that were remembered by the setup_environment function
90 """
90 """
91 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
91 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
92 os.chdir(old_wd)
92 os.chdir(old_wd)
93 reload(path)
93 reload(path)
94
94
95 for key in list(env):
95 for key in list(env):
96 if key not in oldenv:
96 if key not in oldenv:
97 del env[key]
97 del env[key]
98 env.update(oldenv)
98 env.update(oldenv)
99 if hasattr(sys, 'frozen'):
99 if hasattr(sys, 'frozen'):
100 del sys.frozen
100 del sys.frozen
101
101
102
102
103 # Build decorator that uses the setup_environment/setup_environment
103 # Build decorator that uses the setup_environment/setup_environment
104 @pytest.fixture
104 @pytest.fixture
105 def environment():
105 def environment():
106 setup_environment()
106 setup_environment()
107 yield
107 yield
108 teardown_environment()
108 teardown_environment()
109
109
110
110
111 with_environment = pytest.mark.usefixtures("environment")
111 with_environment = pytest.mark.usefixtures("environment")
112
112
113
113
114 @skip_if_not_win32
114 @skip_if_not_win32
115 @with_environment
115 @with_environment
116 def test_get_home_dir_1():
116 def test_get_home_dir_1():
117 """Testcase for py2exe logic, un-compressed lib
117 """Testcase for py2exe logic, un-compressed lib
118 """
118 """
119 unfrozen = path.get_home_dir()
119 unfrozen = path.get_home_dir()
120 sys.frozen = True
120 sys.frozen = True
121
121
122 #fake filename for IPython.__init__
122 #fake filename for IPython.__init__
123 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
123 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
124
124
125 home_dir = path.get_home_dir()
125 home_dir = path.get_home_dir()
126 assert home_dir == unfrozen
126 assert home_dir == unfrozen
127
127
128
128
129 @skip_if_not_win32
129 @skip_if_not_win32
130 @with_environment
130 @with_environment
131 def test_get_home_dir_2():
131 def test_get_home_dir_2():
132 """Testcase for py2exe logic, compressed lib
132 """Testcase for py2exe logic, compressed lib
133 """
133 """
134 unfrozen = path.get_home_dir()
134 unfrozen = path.get_home_dir()
135 sys.frozen = True
135 sys.frozen = True
136 #fake filename for IPython.__init__
136 #fake filename for IPython.__init__
137 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
137 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
138
138
139 home_dir = path.get_home_dir(True)
139 home_dir = path.get_home_dir(True)
140 assert home_dir == unfrozen
140 assert home_dir == unfrozen
141
141
142
142
143 @skip_win32
143 @skip_win32
144 @with_environment
144 @with_environment
145 def test_get_home_dir_3():
145 def test_get_home_dir_3():
146 """get_home_dir() uses $HOME if set"""
146 """get_home_dir() uses $HOME if set"""
147 env["HOME"] = HOME_TEST_DIR
147 env["HOME"] = HOME_TEST_DIR
148 home_dir = path.get_home_dir(True)
148 home_dir = path.get_home_dir(True)
149 # get_home_dir expands symlinks
149 # get_home_dir expands symlinks
150 assert home_dir == os.path.realpath(env["HOME"])
150 assert home_dir == os.path.realpath(env["HOME"])
151
151
152
152
153 @with_environment
153 @with_environment
154 def test_get_home_dir_4():
154 def test_get_home_dir_4():
155 """get_home_dir() still works if $HOME is not set"""
155 """get_home_dir() still works if $HOME is not set"""
156
156
157 if 'HOME' in env: del env['HOME']
157 if 'HOME' in env: del env['HOME']
158 # this should still succeed, but we don't care what the answer is
158 # this should still succeed, but we don't care what the answer is
159 home = path.get_home_dir(False)
159 home = path.get_home_dir(False)
160
160
161 @skip_win32
161 @skip_win32
162 @with_environment
162 @with_environment
163 def test_get_home_dir_5():
163 def test_get_home_dir_5():
164 """raise HomeDirError if $HOME is specified, but not a writable dir"""
164 """raise HomeDirError if $HOME is specified, but not a writable dir"""
165 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
165 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
166 # set os.name = posix, to prevent My Documents fallback on Windows
166 # set os.name = posix, to prevent My Documents fallback on Windows
167 os.name = 'posix'
167 os.name = 'posix'
168 pytest.raises(path.HomeDirError, path.get_home_dir, True)
168 pytest.raises(path.HomeDirError, path.get_home_dir, True)
169
169
170 # Should we stub wreg fully so we can run the test on all platforms?
170 # Should we stub wreg fully so we can run the test on all platforms?
171 @skip_if_not_win32
171 @skip_if_not_win32
172 @with_environment
172 @with_environment
173 def test_get_home_dir_8():
173 def test_get_home_dir_8():
174 """Using registry hack for 'My Documents', os=='nt'
174 """Using registry hack for 'My Documents', os=='nt'
175
175
176 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
176 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
177 """
177 """
178 os.name = 'nt'
178 os.name = 'nt'
179 # Remove from stub environment all keys that may be set
179 # Remove from stub environment all keys that may be set
180 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
180 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
181 env.pop(key, None)
181 env.pop(key, None)
182
182
183 class key:
183 class key:
184 def __enter__(self):
184 def __enter__(self):
185 pass
185 pass
186 def Close(self):
186 def Close(self):
187 pass
187 pass
188 def __exit__(*args, **kwargs):
188 def __exit__(*args, **kwargs):
189 pass
189 pass
190
190
191 with patch.object(wreg, 'OpenKey', return_value=key()), \
191 with patch.object(wreg, 'OpenKey', return_value=key()), \
192 patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]):
192 patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]):
193 home_dir = path.get_home_dir()
193 home_dir = path.get_home_dir()
194 assert home_dir == abspath(HOME_TEST_DIR)
194 assert home_dir == abspath(HOME_TEST_DIR)
195
195
196 @with_environment
196 @with_environment
197 def test_get_xdg_dir_0():
197 def test_get_xdg_dir_0():
198 """test_get_xdg_dir_0, check xdg_dir"""
198 """test_get_xdg_dir_0, check xdg_dir"""
199 reload(path)
199 reload(path)
200 path._writable_dir = lambda path: True
200 path._writable_dir = lambda path: True
201 path.get_home_dir = lambda : 'somewhere'
201 path.get_home_dir = lambda : 'somewhere'
202 os.name = "posix"
202 os.name = "posix"
203 sys.platform = "linux2"
203 sys.platform = "linux2"
204 env.pop('IPYTHON_DIR', None)
204 env.pop('IPYTHON_DIR', None)
205 env.pop('IPYTHONDIR', None)
205 env.pop('IPYTHONDIR', None)
206 env.pop('XDG_CONFIG_HOME', None)
206 env.pop('XDG_CONFIG_HOME', None)
207
207
208 assert path.get_xdg_dir() == os.path.join("somewhere", ".config")
208 assert path.get_xdg_dir() == os.path.join("somewhere", ".config")
209
209
210
210
211 @with_environment
211 @with_environment
212 def test_get_xdg_dir_1():
212 def test_get_xdg_dir_1():
213 """test_get_xdg_dir_1, check nonexistent xdg_dir"""
213 """test_get_xdg_dir_1, check nonexistent xdg_dir"""
214 reload(path)
214 reload(path)
215 path.get_home_dir = lambda : HOME_TEST_DIR
215 path.get_home_dir = lambda : HOME_TEST_DIR
216 os.name = "posix"
216 os.name = "posix"
217 sys.platform = "linux2"
217 sys.platform = "linux2"
218 env.pop('IPYTHON_DIR', None)
218 env.pop('IPYTHON_DIR', None)
219 env.pop('IPYTHONDIR', None)
219 env.pop('IPYTHONDIR', None)
220 env.pop('XDG_CONFIG_HOME', None)
220 env.pop('XDG_CONFIG_HOME', None)
221 assert path.get_xdg_dir() is None
221 assert path.get_xdg_dir() is None
222
222
223 @with_environment
223 @with_environment
224 def test_get_xdg_dir_2():
224 def test_get_xdg_dir_2():
225 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
225 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
226 reload(path)
226 reload(path)
227 path.get_home_dir = lambda : HOME_TEST_DIR
227 path.get_home_dir = lambda : HOME_TEST_DIR
228 os.name = "posix"
228 os.name = "posix"
229 sys.platform = "linux2"
229 sys.platform = "linux2"
230 env.pop('IPYTHON_DIR', None)
230 env.pop('IPYTHON_DIR', None)
231 env.pop('IPYTHONDIR', None)
231 env.pop('IPYTHONDIR', None)
232 env.pop('XDG_CONFIG_HOME', None)
232 env.pop('XDG_CONFIG_HOME', None)
233 cfgdir=os.path.join(path.get_home_dir(), '.config')
233 cfgdir=os.path.join(path.get_home_dir(), '.config')
234 if not os.path.exists(cfgdir):
234 if not os.path.exists(cfgdir):
235 os.makedirs(cfgdir)
235 os.makedirs(cfgdir)
236
236
237 assert path.get_xdg_dir() == cfgdir
237 assert path.get_xdg_dir() == cfgdir
238
238
239 @with_environment
239 @with_environment
240 def test_get_xdg_dir_3():
240 def test_get_xdg_dir_3():
241 """test_get_xdg_dir_3, check xdg_dir not used on OS X"""
241 """test_get_xdg_dir_3, check xdg_dir not used on non-posix systems"""
242 reload(path)
242 reload(path)
243 path.get_home_dir = lambda : HOME_TEST_DIR
243 path.get_home_dir = lambda : HOME_TEST_DIR
244 os.name = "posix"
244 os.name = "nt"
245 sys.platform = "darwin"
245 sys.platform = "win32"
246 env.pop('IPYTHON_DIR', None)
246 env.pop('IPYTHON_DIR', None)
247 env.pop('IPYTHONDIR', None)
247 env.pop('IPYTHONDIR', None)
248 env.pop('XDG_CONFIG_HOME', None)
248 env.pop('XDG_CONFIG_HOME', None)
249 cfgdir=os.path.join(path.get_home_dir(), '.config')
249 cfgdir=os.path.join(path.get_home_dir(), '.config')
250 os.makedirs(cfgdir, exist_ok=True)
250 os.makedirs(cfgdir, exist_ok=True)
251
251
252 assert path.get_xdg_dir() is None
252 assert path.get_xdg_dir() is None
253
253
254 def test_filefind():
254 def test_filefind():
255 """Various tests for filefind"""
255 """Various tests for filefind"""
256 f = tempfile.NamedTemporaryFile()
256 f = tempfile.NamedTemporaryFile()
257 # print 'fname:',f.name
257 # print 'fname:',f.name
258 alt_dirs = paths.get_ipython_dir()
258 alt_dirs = paths.get_ipython_dir()
259 t = path.filefind(f.name, alt_dirs)
259 t = path.filefind(f.name, alt_dirs)
260 # print 'found:',t
260 # print 'found:',t
261
261
262
262
263 @dec.skip_if_not_win32
263 @dec.skip_if_not_win32
264 def test_get_long_path_name_win32():
264 def test_get_long_path_name_win32():
265 with TemporaryDirectory() as tmpdir:
265 with TemporaryDirectory() as tmpdir:
266
266
267 # Make a long path. Expands the path of tmpdir prematurely as it may already have a long
267 # Make a long path. Expands the path of tmpdir prematurely as it may already have a long
268 # path component, so ensure we include the long form of it
268 # path component, so ensure we include the long form of it
269 long_path = os.path.join(path.get_long_path_name(tmpdir), 'this is my long path name')
269 long_path = os.path.join(path.get_long_path_name(tmpdir), 'this is my long path name')
270 os.makedirs(long_path)
270 os.makedirs(long_path)
271
271
272 # Test to see if the short path evaluates correctly.
272 # Test to see if the short path evaluates correctly.
273 short_path = os.path.join(tmpdir, 'THISIS~1')
273 short_path = os.path.join(tmpdir, 'THISIS~1')
274 evaluated_path = path.get_long_path_name(short_path)
274 evaluated_path = path.get_long_path_name(short_path)
275 assert evaluated_path.lower() == long_path.lower()
275 assert evaluated_path.lower() == long_path.lower()
276
276
277
277
278 @dec.skip_win32
278 @dec.skip_win32
279 def test_get_long_path_name():
279 def test_get_long_path_name():
280 p = path.get_long_path_name("/usr/local")
280 p = path.get_long_path_name("/usr/local")
281 assert p == "/usr/local"
281 assert p == "/usr/local"
282
282
283
283
284 class TestRaiseDeprecation(unittest.TestCase):
284 class TestRaiseDeprecation(unittest.TestCase):
285
285
286 @dec.skip_win32 # can't create not-user-writable dir on win
286 @dec.skip_win32 # can't create not-user-writable dir on win
287 @with_environment
287 @with_environment
288 def test_not_writable_ipdir(self):
288 def test_not_writable_ipdir(self):
289 tmpdir = tempfile.mkdtemp()
289 tmpdir = tempfile.mkdtemp()
290 os.name = "posix"
290 os.name = "posix"
291 env.pop('IPYTHON_DIR', None)
291 env.pop('IPYTHON_DIR', None)
292 env.pop('IPYTHONDIR', None)
292 env.pop('IPYTHONDIR', None)
293 env.pop('XDG_CONFIG_HOME', None)
293 env.pop('XDG_CONFIG_HOME', None)
294 env['HOME'] = tmpdir
294 env['HOME'] = tmpdir
295 ipdir = os.path.join(tmpdir, '.ipython')
295 ipdir = os.path.join(tmpdir, '.ipython')
296 os.mkdir(ipdir, 0o555)
296 os.mkdir(ipdir, 0o555)
297 try:
297 try:
298 open(os.path.join(ipdir, "_foo_"), 'w').close()
298 open(os.path.join(ipdir, "_foo_"), 'w').close()
299 except IOError:
299 except IOError:
300 pass
300 pass
301 else:
301 else:
302 # I can still write to an unwritable dir,
302 # I can still write to an unwritable dir,
303 # assume I'm root and skip the test
303 # assume I'm root and skip the test
304 pytest.skip("I can't create directories that I can't write to")
304 pytest.skip("I can't create directories that I can't write to")
305
305
306 with self.assertWarnsRegex(UserWarning, 'is not a writable location'):
306 with self.assertWarnsRegex(UserWarning, 'is not a writable location'):
307 ipdir = paths.get_ipython_dir()
307 ipdir = paths.get_ipython_dir()
308 env.pop('IPYTHON_DIR', None)
308 env.pop('IPYTHON_DIR', None)
309
309
310 @with_environment
310 @with_environment
311 def test_get_py_filename():
311 def test_get_py_filename():
312 os.chdir(TMP_TEST_DIR)
312 os.chdir(TMP_TEST_DIR)
313 with make_tempfile("foo.py"):
313 with make_tempfile("foo.py"):
314 assert path.get_py_filename("foo.py") == "foo.py"
314 assert path.get_py_filename("foo.py") == "foo.py"
315 assert path.get_py_filename("foo") == "foo.py"
315 assert path.get_py_filename("foo") == "foo.py"
316 with make_tempfile("foo"):
316 with make_tempfile("foo"):
317 assert path.get_py_filename("foo") == "foo"
317 assert path.get_py_filename("foo") == "foo"
318 pytest.raises(IOError, path.get_py_filename, "foo.py")
318 pytest.raises(IOError, path.get_py_filename, "foo.py")
319 pytest.raises(IOError, path.get_py_filename, "foo")
319 pytest.raises(IOError, path.get_py_filename, "foo")
320 pytest.raises(IOError, path.get_py_filename, "foo.py")
320 pytest.raises(IOError, path.get_py_filename, "foo.py")
321 true_fn = "foo with spaces.py"
321 true_fn = "foo with spaces.py"
322 with make_tempfile(true_fn):
322 with make_tempfile(true_fn):
323 assert path.get_py_filename("foo with spaces") == true_fn
323 assert path.get_py_filename("foo with spaces") == true_fn
324 assert path.get_py_filename("foo with spaces.py") == true_fn
324 assert path.get_py_filename("foo with spaces.py") == true_fn
325 pytest.raises(IOError, path.get_py_filename, '"foo with spaces.py"')
325 pytest.raises(IOError, path.get_py_filename, '"foo with spaces.py"')
326 pytest.raises(IOError, path.get_py_filename, "'foo with spaces.py'")
326 pytest.raises(IOError, path.get_py_filename, "'foo with spaces.py'")
327
327
328 @onlyif_unicode_paths
328 @onlyif_unicode_paths
329 def test_unicode_in_filename():
329 def test_unicode_in_filename():
330 """When a file doesn't exist, the exception raised should be safe to call
330 """When a file doesn't exist, the exception raised should be safe to call
331 str() on - i.e. in Python 2 it must only have ASCII characters.
331 str() on - i.e. in Python 2 it must only have ASCII characters.
332
332
333 https://github.com/ipython/ipython/issues/875
333 https://github.com/ipython/ipython/issues/875
334 """
334 """
335 try:
335 try:
336 # these calls should not throw unicode encode exceptions
336 # these calls should not throw unicode encode exceptions
337 path.get_py_filename('fooéè.py')
337 path.get_py_filename('fooéè.py')
338 except IOError as ex:
338 except IOError as ex:
339 str(ex)
339 str(ex)
340
340
341
341
342 class TestShellGlob(unittest.TestCase):
342 class TestShellGlob(unittest.TestCase):
343
343
344 @classmethod
344 @classmethod
345 def setUpClass(cls):
345 def setUpClass(cls):
346 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
346 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
347 cls.filenames_end_with_b = ['0b', '1b', '2b']
347 cls.filenames_end_with_b = ['0b', '1b', '2b']
348 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
348 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
349 cls.tempdir = TemporaryDirectory()
349 cls.tempdir = TemporaryDirectory()
350 td = cls.tempdir.name
350 td = cls.tempdir.name
351
351
352 with cls.in_tempdir():
352 with cls.in_tempdir():
353 # Create empty files
353 # Create empty files
354 for fname in cls.filenames:
354 for fname in cls.filenames:
355 open(os.path.join(td, fname), 'w').close()
355 open(os.path.join(td, fname), 'w').close()
356
356
357 @classmethod
357 @classmethod
358 def tearDownClass(cls):
358 def tearDownClass(cls):
359 cls.tempdir.cleanup()
359 cls.tempdir.cleanup()
360
360
361 @classmethod
361 @classmethod
362 @contextmanager
362 @contextmanager
363 def in_tempdir(cls):
363 def in_tempdir(cls):
364 save = os.getcwd()
364 save = os.getcwd()
365 try:
365 try:
366 os.chdir(cls.tempdir.name)
366 os.chdir(cls.tempdir.name)
367 yield
367 yield
368 finally:
368 finally:
369 os.chdir(save)
369 os.chdir(save)
370
370
371 def check_match(self, patterns, matches):
371 def check_match(self, patterns, matches):
372 with self.in_tempdir():
372 with self.in_tempdir():
373 # glob returns unordered list. that's why sorted is required.
373 # glob returns unordered list. that's why sorted is required.
374 assert sorted(path.shellglob(patterns)) == sorted(matches)
374 assert sorted(path.shellglob(patterns)) == sorted(matches)
375
375
376 def common_cases(self):
376 def common_cases(self):
377 return [
377 return [
378 (['*'], self.filenames),
378 (['*'], self.filenames),
379 (['a*'], self.filenames_start_with_a),
379 (['a*'], self.filenames_start_with_a),
380 (['*c'], ['*c']),
380 (['*c'], ['*c']),
381 (['*', 'a*', '*b', '*c'], self.filenames
381 (['*', 'a*', '*b', '*c'], self.filenames
382 + self.filenames_start_with_a
382 + self.filenames_start_with_a
383 + self.filenames_end_with_b
383 + self.filenames_end_with_b
384 + ['*c']),
384 + ['*c']),
385 (['a[012]'], self.filenames_start_with_a),
385 (['a[012]'], self.filenames_start_with_a),
386 ]
386 ]
387
387
388 @skip_win32
388 @skip_win32
389 def test_match_posix(self):
389 def test_match_posix(self):
390 for (patterns, matches) in self.common_cases() + [
390 for (patterns, matches) in self.common_cases() + [
391 ([r'\*'], ['*']),
391 ([r'\*'], ['*']),
392 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
392 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
393 ([r'a\[012]'], ['a[012]']),
393 ([r'a\[012]'], ['a[012]']),
394 ]:
394 ]:
395 self.check_match(patterns, matches)
395 self.check_match(patterns, matches)
396
396
397 @skip_if_not_win32
397 @skip_if_not_win32
398 def test_match_windows(self):
398 def test_match_windows(self):
399 for (patterns, matches) in self.common_cases() + [
399 for (patterns, matches) in self.common_cases() + [
400 # In windows, backslash is interpreted as path
400 # In windows, backslash is interpreted as path
401 # separator. Therefore, you can't escape glob
401 # separator. Therefore, you can't escape glob
402 # using it.
402 # using it.
403 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
403 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
404 ([r'a\[012]'], [r'a\[012]']),
404 ([r'a\[012]'], [r'a\[012]']),
405 ]:
405 ]:
406 self.check_match(patterns, matches)
406 self.check_match(patterns, matches)
407
407
408
408
409 # TODO : pytest.mark.parametrise once nose is gone.
409 # TODO : pytest.mark.parametrise once nose is gone.
410 def test_unescape_glob():
410 def test_unescape_glob():
411 assert path.unescape_glob(r"\*\[\!\]\?") == "*[!]?"
411 assert path.unescape_glob(r"\*\[\!\]\?") == "*[!]?"
412 assert path.unescape_glob(r"\\*") == r"\*"
412 assert path.unescape_glob(r"\\*") == r"\*"
413 assert path.unescape_glob(r"\\\*") == r"\*"
413 assert path.unescape_glob(r"\\\*") == r"\*"
414 assert path.unescape_glob(r"\\a") == r"\a"
414 assert path.unescape_glob(r"\\a") == r"\a"
415 assert path.unescape_glob(r"\a") == r"\a"
415 assert path.unescape_glob(r"\a") == r"\a"
416
416
417
417
418 @onlyif_unicode_paths
418 @onlyif_unicode_paths
419 def test_ensure_dir_exists():
419 def test_ensure_dir_exists():
420 with TemporaryDirectory() as td:
420 with TemporaryDirectory() as td:
421 d = os.path.join(td, 'βˆ‚ir')
421 d = os.path.join(td, 'βˆ‚ir')
422 path.ensure_dir_exists(d) # create it
422 path.ensure_dir_exists(d) # create it
423 assert os.path.isdir(d)
423 assert os.path.isdir(d)
424 path.ensure_dir_exists(d) # no-op
424 path.ensure_dir_exists(d) # no-op
425 f = os.path.join(td, 'Ζ’ile')
425 f = os.path.join(td, 'Ζ’ile')
426 open(f, 'w').close() # touch
426 open(f, 'w').close() # touch
427 with pytest.raises(IOError):
427 with pytest.raises(IOError):
428 path.ensure_dir_exists(f)
428 path.ensure_dir_exists(f)
429
429
430 class TestLinkOrCopy(unittest.TestCase):
430 class TestLinkOrCopy(unittest.TestCase):
431 def setUp(self):
431 def setUp(self):
432 self.tempdir = TemporaryDirectory()
432 self.tempdir = TemporaryDirectory()
433 self.src = self.dst("src")
433 self.src = self.dst("src")
434 with open(self.src, "w") as f:
434 with open(self.src, "w") as f:
435 f.write("Hello, world!")
435 f.write("Hello, world!")
436
436
437 def tearDown(self):
437 def tearDown(self):
438 self.tempdir.cleanup()
438 self.tempdir.cleanup()
439
439
440 def dst(self, *args):
440 def dst(self, *args):
441 return os.path.join(self.tempdir.name, *args)
441 return os.path.join(self.tempdir.name, *args)
442
442
443 def assert_inode_not_equal(self, a, b):
443 def assert_inode_not_equal(self, a, b):
444 assert (
444 assert (
445 os.stat(a).st_ino != os.stat(b).st_ino
445 os.stat(a).st_ino != os.stat(b).st_ino
446 ), "%r and %r do reference the same indoes" % (a, b)
446 ), "%r and %r do reference the same indoes" % (a, b)
447
447
448 def assert_inode_equal(self, a, b):
448 def assert_inode_equal(self, a, b):
449 assert (
449 assert (
450 os.stat(a).st_ino == os.stat(b).st_ino
450 os.stat(a).st_ino == os.stat(b).st_ino
451 ), "%r and %r do not reference the same indoes" % (a, b)
451 ), "%r and %r do not reference the same indoes" % (a, b)
452
452
453 def assert_content_equal(self, a, b):
453 def assert_content_equal(self, a, b):
454 with open(a) as a_f:
454 with open(a) as a_f:
455 with open(b) as b_f:
455 with open(b) as b_f:
456 assert a_f.read() == b_f.read()
456 assert a_f.read() == b_f.read()
457
457
458 @skip_win32
458 @skip_win32
459 def test_link_successful(self):
459 def test_link_successful(self):
460 dst = self.dst("target")
460 dst = self.dst("target")
461 path.link_or_copy(self.src, dst)
461 path.link_or_copy(self.src, dst)
462 self.assert_inode_equal(self.src, dst)
462 self.assert_inode_equal(self.src, dst)
463
463
464 @skip_win32
464 @skip_win32
465 def test_link_into_dir(self):
465 def test_link_into_dir(self):
466 dst = self.dst("some_dir")
466 dst = self.dst("some_dir")
467 os.mkdir(dst)
467 os.mkdir(dst)
468 path.link_or_copy(self.src, dst)
468 path.link_or_copy(self.src, dst)
469 expected_dst = self.dst("some_dir", os.path.basename(self.src))
469 expected_dst = self.dst("some_dir", os.path.basename(self.src))
470 self.assert_inode_equal(self.src, expected_dst)
470 self.assert_inode_equal(self.src, expected_dst)
471
471
472 @skip_win32
472 @skip_win32
473 def test_target_exists(self):
473 def test_target_exists(self):
474 dst = self.dst("target")
474 dst = self.dst("target")
475 open(dst, "w").close()
475 open(dst, "w").close()
476 path.link_or_copy(self.src, dst)
476 path.link_or_copy(self.src, dst)
477 self.assert_inode_equal(self.src, dst)
477 self.assert_inode_equal(self.src, dst)
478
478
479 @skip_win32
479 @skip_win32
480 def test_no_link(self):
480 def test_no_link(self):
481 real_link = os.link
481 real_link = os.link
482 try:
482 try:
483 del os.link
483 del os.link
484 dst = self.dst("target")
484 dst = self.dst("target")
485 path.link_or_copy(self.src, dst)
485 path.link_or_copy(self.src, dst)
486 self.assert_content_equal(self.src, dst)
486 self.assert_content_equal(self.src, dst)
487 self.assert_inode_not_equal(self.src, dst)
487 self.assert_inode_not_equal(self.src, dst)
488 finally:
488 finally:
489 os.link = real_link
489 os.link = real_link
490
490
491 @skip_if_not_win32
491 @skip_if_not_win32
492 def test_windows(self):
492 def test_windows(self):
493 dst = self.dst("target")
493 dst = self.dst("target")
494 path.link_or_copy(self.src, dst)
494 path.link_or_copy(self.src, dst)
495 self.assert_content_equal(self.src, dst)
495 self.assert_content_equal(self.src, dst)
496
496
497 def test_link_twice(self):
497 def test_link_twice(self):
498 # Linking the same file twice shouldn't leave duplicates around.
498 # Linking the same file twice shouldn't leave duplicates around.
499 # See https://github.com/ipython/ipython/issues/6450
499 # See https://github.com/ipython/ipython/issues/6450
500 dst = self.dst('target')
500 dst = self.dst('target')
501 path.link_or_copy(self.src, dst)
501 path.link_or_copy(self.src, dst)
502 path.link_or_copy(self.src, dst)
502 path.link_or_copy(self.src, dst)
503 self.assert_inode_equal(self.src, dst)
503 self.assert_inode_equal(self.src, dst)
504 assert sorted(os.listdir(self.tempdir.name)) == ["src", "target"]
504 assert sorted(os.listdir(self.tempdir.name)) == ["src", "target"]
General Comments 0
You need to be logged in to leave comments. Login now