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