##// END OF EJS Templates
Apply suggestions from code review...
M Bussonnier -
Show More
@@ -1,244 +1,244
1 # encoding: utf-8
1 # encoding: utf-8
2 """An object for managing IPython profile directories."""
2 """An object for managing IPython profile directories."""
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 errno
9 import errno
10 from pathlib import Path
10 from pathlib import Path
11
11
12 from traitlets.config.configurable import LoggingConfigurable
12 from traitlets.config.configurable import LoggingConfigurable
13 from ..paths import get_ipython_package_dir
13 from ..paths import get_ipython_package_dir
14 from ..utils.path import expand_path, ensure_dir_exists
14 from ..utils.path import expand_path, ensure_dir_exists
15 from traitlets import Unicode, Bool, observe
15 from traitlets import Unicode, Bool, observe
16
16
17 from typing import Optional
17 from typing import Optional
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Module errors
20 # Module errors
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 class ProfileDirError(Exception):
23 class ProfileDirError(Exception):
24 pass
24 pass
25
25
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Class for managing profile directories
28 # Class for managing profile directories
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 class ProfileDir(LoggingConfigurable):
31 class ProfileDir(LoggingConfigurable):
32 """An object to manage the profile directory and its resources.
32 """An object to manage the profile directory and its resources.
33
33
34 The profile directory is used by all IPython applications, to manage
34 The profile directory is used by all IPython applications, to manage
35 configuration, logging and security.
35 configuration, logging and security.
36
36
37 This object knows how to find, create and manage these directories. This
37 This object knows how to find, create and manage these directories. This
38 should be used by any code that wants to handle profiles.
38 should be used by any code that wants to handle profiles.
39 """
39 """
40
40
41 security_dir_name = Unicode('security')
41 security_dir_name = Unicode('security')
42 log_dir_name = Unicode('log')
42 log_dir_name = Unicode('log')
43 startup_dir_name = Unicode('startup')
43 startup_dir_name = Unicode('startup')
44 pid_dir_name = Unicode('pid')
44 pid_dir_name = Unicode('pid')
45 static_dir_name = Unicode('static')
45 static_dir_name = Unicode('static')
46 security_dir = Unicode(u'')
46 security_dir = Unicode(u'')
47 log_dir = Unicode(u'')
47 log_dir = Unicode(u'')
48 startup_dir = Unicode(u'')
48 startup_dir = Unicode(u'')
49 pid_dir = Unicode(u'')
49 pid_dir = Unicode(u'')
50 static_dir = Unicode(u'')
50 static_dir = Unicode(u'')
51
51
52 location = Unicode(u'',
52 location = Unicode(u'',
53 help="""Set the profile location directly. This overrides the logic used by the
53 help="""Set the profile location directly. This overrides the logic used by the
54 `profile` option.""",
54 `profile` option.""",
55 ).tag(config=True)
55 ).tag(config=True)
56
56
57 _location_isset = Bool(False) # flag for detecting multiply set location
57 _location_isset = Bool(False) # flag for detecting multiply set location
58 @observe('location')
58 @observe('location')
59 def _location_changed(self, change):
59 def _location_changed(self, change):
60 if self._location_isset:
60 if self._location_isset:
61 raise RuntimeError("Cannot set profile location more than once.")
61 raise RuntimeError("Cannot set profile location more than once.")
62 self._location_isset = True
62 self._location_isset = True
63 new = change['new']
63 new = change['new']
64 ensure_dir_exists(new)
64 ensure_dir_exists(new)
65
65
66 # ensure config files exist:
66 # ensure config files exist:
67 self.security_dir = os.path.join(new, self.security_dir_name)
67 self.security_dir = os.path.join(new, self.security_dir_name)
68 self.log_dir = os.path.join(new, self.log_dir_name)
68 self.log_dir = os.path.join(new, self.log_dir_name)
69 self.startup_dir = os.path.join(new, self.startup_dir_name)
69 self.startup_dir = os.path.join(new, self.startup_dir_name)
70 self.pid_dir = os.path.join(new, self.pid_dir_name)
70 self.pid_dir = os.path.join(new, self.pid_dir_name)
71 self.static_dir = os.path.join(new, self.static_dir_name)
71 self.static_dir = os.path.join(new, self.static_dir_name)
72 self.check_dirs()
72 self.check_dirs()
73
73
74 def _mkdir(self, path: str, mode: Optional[int] = None) -> bool:
74 def _mkdir(self, path: str, mode: Optional[int] = None) -> bool:
75 """ensure a directory exists at a given path
75 """ensure a directory exists at a given path
76
76
77 This is a version of os.mkdir, with the following differences:
77 This is a version of os.mkdir, with the following differences:
78
78
79 - returns wether the directory has been created or not.
79 - returns whether the directory has been created or not.
80 - ignores EEXIST, protecting against race conditions where
80 - ignores EEXIST, protecting against race conditions where
81 the dir may have been created in between the check and
81 the dir may have been created in between the check and
82 the creation
82 the creation
83 - sets permissions if requested and the dir already exists
83 - sets permissions if requested and the dir already exists
84
84
85 Parameters
85 Parameters
86 ----------
86 ----------
87 path: str
87 path: str
88 path of the dir to create
88 path of the dir to create
89 mode: int
89 mode: int
90 see `mode` of `os.mkdir`
90 see `mode` of `os.mkdir`
91
91
92 Returns
92 Returns
93 -------
93 -------
94 bool:
94 bool:
95 returns True if it created the directory, False otherwise
95 returns True if it created the directory, False otherwise
96 """
96 """
97
97
98 if os.path.exists(path):
98 if os.path.exists(path):
99 if mode and os.stat(path).st_mode != mode:
99 if mode and os.stat(path).st_mode != mode:
100 try:
100 try:
101 os.chmod(path, mode)
101 os.chmod(path, mode)
102 except OSError:
102 except OSError:
103 self.log.warning(
103 self.log.warning(
104 "Could not set permissions on %s",
104 "Could not set permissions on %s",
105 path
105 path
106 )
106 )
107 return False
107 return False
108 try:
108 try:
109 if mode:
109 if mode:
110 os.mkdir(path, mode)
110 os.mkdir(path, mode)
111 else:
111 else:
112 os.mkdir(path)
112 os.mkdir(path)
113 except OSError as e:
113 except OSError as e:
114 if e.errno == errno.EEXIST:
114 if e.errno == errno.EEXIST:
115 return False
115 return False
116 else:
116 else:
117 raise
117 raise
118
118
119 return True
119 return True
120
120
121 @observe('log_dir')
121 @observe('log_dir')
122 def check_log_dir(self, change=None):
122 def check_log_dir(self, change=None):
123 self._mkdir(self.log_dir)
123 self._mkdir(self.log_dir)
124
124
125 @observe('startup_dir')
125 @observe('startup_dir')
126 def check_startup_dir(self, change=None):
126 def check_startup_dir(self, change=None):
127 if self._mkdir(self.startup_dir):
127 if self._mkdir(self.startup_dir):
128 readme = os.path.join(self.startup_dir, "README")
128 readme = os.path.join(self.startup_dir, "README")
129 src = os.path.join(
129 src = os.path.join(
130 get_ipython_package_dir(), "core", "profile", "README_STARTUP"
130 get_ipython_package_dir(), "core", "profile", "README_STARTUP"
131 )
131 )
132
132
133 if not os.path.exists(src):
133 if os.path.exists(src):
134 if not os.path.exists(readme):
135 shutil.copy(src, readme)
136 else:
134 self.log.warning(
137 self.log.warning(
135 "Could not copy README_STARTUP to startup dir. Source file %s does not exist.",
138 "Could not copy README_STARTUP to startup dir. Source file %s does not exist.",
136 src,
139 src,
137 )
140 )
138
141
139 if os.path.exists(src) and not os.path.exists(readme):
140 shutil.copy(src, readme)
141
142 @observe('security_dir')
142 @observe('security_dir')
143 def check_security_dir(self, change=None):
143 def check_security_dir(self, change=None):
144 self._mkdir(self.security_dir, 0o40700)
144 self._mkdir(self.security_dir, 0o40700)
145
145
146 @observe('pid_dir')
146 @observe('pid_dir')
147 def check_pid_dir(self, change=None):
147 def check_pid_dir(self, change=None):
148 self._mkdir(self.pid_dir, 0o40700)
148 self._mkdir(self.pid_dir, 0o40700)
149
149
150 def check_dirs(self):
150 def check_dirs(self):
151 self.check_security_dir()
151 self.check_security_dir()
152 self.check_log_dir()
152 self.check_log_dir()
153 self.check_pid_dir()
153 self.check_pid_dir()
154 self.check_startup_dir()
154 self.check_startup_dir()
155
155
156 def copy_config_file(self, config_file: str, path: Path, overwrite=False) -> bool:
156 def copy_config_file(self, config_file: str, path: Path, overwrite=False) -> bool:
157 """Copy a default config file into the active profile directory.
157 """Copy a default config file into the active profile directory.
158
158
159 Default configuration files are kept in :mod:`IPython.core.profile`.
159 Default configuration files are kept in :mod:`IPython.core.profile`.
160 This function moves these from that location to the working profile
160 This function moves these from that location to the working profile
161 directory.
161 directory.
162 """
162 """
163 dst = Path(os.path.join(self.location, config_file))
163 dst = Path(os.path.join(self.location, config_file))
164 if dst.exists() and not overwrite:
164 if dst.exists() and not overwrite:
165 return False
165 return False
166 if path is None:
166 if path is None:
167 path = os.path.join(get_ipython_package_dir(), u'core', u'profile', u'default')
167 path = os.path.join(get_ipython_package_dir(), u'core', u'profile', u'default')
168 assert isinstance(path, Path)
168 assert isinstance(path, Path)
169 src = path / config_file
169 src = path / config_file
170 shutil.copy(src, dst)
170 shutil.copy(src, dst)
171 return True
171 return True
172
172
173 @classmethod
173 @classmethod
174 def create_profile_dir(cls, profile_dir, config=None):
174 def create_profile_dir(cls, profile_dir, config=None):
175 """Create a new profile directory given a full path.
175 """Create a new profile directory given a full path.
176
176
177 Parameters
177 Parameters
178 ----------
178 ----------
179 profile_dir : str
179 profile_dir : str
180 The full path to the profile directory. If it does exist, it will
180 The full path to the profile directory. If it does exist, it will
181 be used. If not, it will be created.
181 be used. If not, it will be created.
182 """
182 """
183 return cls(location=profile_dir, config=config)
183 return cls(location=profile_dir, config=config)
184
184
185 @classmethod
185 @classmethod
186 def create_profile_dir_by_name(cls, path, name=u'default', config=None):
186 def create_profile_dir_by_name(cls, path, name=u'default', config=None):
187 """Create a profile dir by profile name and path.
187 """Create a profile dir by profile name and path.
188
188
189 Parameters
189 Parameters
190 ----------
190 ----------
191 path : unicode
191 path : unicode
192 The path (directory) to put the profile directory in.
192 The path (directory) to put the profile directory in.
193 name : unicode
193 name : unicode
194 The name of the profile. The name of the profile directory will
194 The name of the profile. The name of the profile directory will
195 be "profile_<profile>".
195 be "profile_<profile>".
196 """
196 """
197 if not os.path.isdir(path):
197 if not os.path.isdir(path):
198 raise ProfileDirError('Directory not found: %s' % path)
198 raise ProfileDirError('Directory not found: %s' % path)
199 profile_dir = os.path.join(path, u'profile_' + name)
199 profile_dir = os.path.join(path, u'profile_' + name)
200 return cls(location=profile_dir, config=config)
200 return cls(location=profile_dir, config=config)
201
201
202 @classmethod
202 @classmethod
203 def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None):
203 def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None):
204 """Find an existing profile dir by profile name, return its ProfileDir.
204 """Find an existing profile dir by profile name, return its ProfileDir.
205
205
206 This searches through a sequence of paths for a profile dir. If it
206 This searches through a sequence of paths for a profile dir. If it
207 is not found, a :class:`ProfileDirError` exception will be raised.
207 is not found, a :class:`ProfileDirError` exception will be raised.
208
208
209 The search path algorithm is:
209 The search path algorithm is:
210 1. ``os.getcwd()`` # removed for security reason.
210 1. ``os.getcwd()`` # removed for security reason.
211 2. ``ipython_dir``
211 2. ``ipython_dir``
212
212
213 Parameters
213 Parameters
214 ----------
214 ----------
215 ipython_dir : unicode or str
215 ipython_dir : unicode or str
216 The IPython directory to use.
216 The IPython directory to use.
217 name : unicode or str
217 name : unicode or str
218 The name of the profile. The name of the profile directory
218 The name of the profile. The name of the profile directory
219 will be "profile_<profile>".
219 will be "profile_<profile>".
220 """
220 """
221 dirname = u'profile_' + name
221 dirname = u'profile_' + name
222 paths = [ipython_dir]
222 paths = [ipython_dir]
223 for p in paths:
223 for p in paths:
224 profile_dir = os.path.join(p, dirname)
224 profile_dir = os.path.join(p, dirname)
225 if os.path.isdir(profile_dir):
225 if os.path.isdir(profile_dir):
226 return cls(location=profile_dir, config=config)
226 return cls(location=profile_dir, config=config)
227 else:
227 else:
228 raise ProfileDirError('Profile directory not found in paths: %s' % dirname)
228 raise ProfileDirError('Profile directory not found in paths: %s' % dirname)
229
229
230 @classmethod
230 @classmethod
231 def find_profile_dir(cls, profile_dir, config=None):
231 def find_profile_dir(cls, profile_dir, config=None):
232 """Find/create a profile dir and return its ProfileDir.
232 """Find/create a profile dir and return its ProfileDir.
233
233
234 This will create the profile directory if it doesn't exist.
234 This will create the profile directory if it doesn't exist.
235
235
236 Parameters
236 Parameters
237 ----------
237 ----------
238 profile_dir : unicode or str
238 profile_dir : unicode or str
239 The path of the profile directory.
239 The path of the profile directory.
240 """
240 """
241 profile_dir = expand_path(profile_dir)
241 profile_dir = expand_path(profile_dir)
242 if not os.path.isdir(profile_dir):
242 if not os.path.isdir(profile_dir):
243 raise ProfileDirError('Profile directory not found: %s' % profile_dir)
243 raise ProfileDirError('Profile directory not found: %s' % profile_dir)
244 return cls(location=profile_dir, config=config)
244 return cls(location=profile_dir, config=config)
General Comments 0
You need to be logged in to leave comments. Login now