##// END OF EJS Templates
Backport PR #4934: `ipython profile create` respects `--ipython-dir`...
Thomas Kluyver -
Show More
@@ -1,310 +1,310 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for managing IPython profiles.
4 4
5 5 To be invoked as the `ipython profile` subcommand.
6 6
7 7 Authors:
8 8
9 9 * Min RK
10 10
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Copyright (C) 2008 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Imports
22 22 #-----------------------------------------------------------------------------
23 23
24 24 import os
25 25
26 26 from IPython.config.application import Application
27 27 from IPython.core.application import (
28 28 BaseIPythonApplication, base_flags
29 29 )
30 30 from IPython.core.profiledir import ProfileDir
31 31 from IPython.utils.importstring import import_item
32 32 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
33 33 from IPython.utils.traitlets import Unicode, Bool, Dict
34 34
35 35 #-----------------------------------------------------------------------------
36 36 # Constants
37 37 #-----------------------------------------------------------------------------
38 38
39 39 create_help = """Create an IPython profile by name
40 40
41 41 Create an ipython profile directory by its name or
42 42 profile directory path. Profile directories contain
43 43 configuration, log and security related files and are named
44 44 using the convention 'profile_<name>'. By default they are
45 45 located in your ipython directory. Once created, you will
46 46 can edit the configuration files in the profile
47 47 directory to configure IPython. Most users will create a
48 48 profile directory by name,
49 49 `ipython profile create myprofile`, which will put the directory
50 50 in `<ipython_dir>/profile_myprofile`.
51 51 """
52 52 list_help = """List available IPython profiles
53 53
54 54 List all available profiles, by profile location, that can
55 55 be found in the current working directly or in the ipython
56 56 directory. Profile directories are named using the convention
57 57 'profile_<profile>'.
58 58 """
59 59 profile_help = """Manage IPython profiles
60 60
61 61 Profile directories contain
62 62 configuration, log and security related files and are named
63 63 using the convention 'profile_<name>'. By default they are
64 64 located in your ipython directory. You can create profiles
65 65 with `ipython profile create <name>`, or see the profiles you
66 66 already have with `ipython profile list`
67 67
68 68 To get started configuring IPython, simply do:
69 69
70 70 $> ipython profile create
71 71
72 72 and IPython will create the default profile in <ipython_dir>/profile_default,
73 73 where you can edit ipython_config.py to start configuring IPython.
74 74
75 75 """
76 76
77 77 _list_examples = "ipython profile list # list all profiles"
78 78
79 79 _create_examples = """
80 80 ipython profile create foo # create profile foo w/ default config files
81 81 ipython profile create foo --reset # restage default config files over current
82 82 ipython profile create foo --parallel # also stage parallel config files
83 83 """
84 84
85 85 _main_examples = """
86 86 ipython profile create -h # show the help string for the create subcommand
87 87 ipython profile list -h # show the help string for the list subcommand
88 88
89 89 ipython locate profile foo # print the path to the directory for profile 'foo'
90 90 """
91 91
92 92 #-----------------------------------------------------------------------------
93 93 # Profile Application Class (for `ipython profile` subcommand)
94 94 #-----------------------------------------------------------------------------
95 95
96 96
97 97 def list_profiles_in(path):
98 98 """list profiles in a given root directory"""
99 99 files = os.listdir(path)
100 100 profiles = []
101 101 for f in files:
102 102 full_path = os.path.join(path, f)
103 103 if os.path.isdir(full_path) and f.startswith('profile_'):
104 104 profiles.append(f.split('_',1)[-1])
105 105 return profiles
106 106
107 107
108 108 def list_bundled_profiles():
109 109 """list profiles that are bundled with IPython."""
110 110 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
111 111 files = os.listdir(path)
112 112 profiles = []
113 113 for profile in files:
114 114 full_path = os.path.join(path, profile)
115 115 if os.path.isdir(full_path) and profile != "__pycache__":
116 116 profiles.append(profile)
117 117 return profiles
118 118
119 119
120 120 class ProfileLocate(BaseIPythonApplication):
121 121 description = """print the path an IPython profile dir"""
122 122
123 123 def parse_command_line(self, argv=None):
124 124 super(ProfileLocate, self).parse_command_line(argv)
125 125 if self.extra_args:
126 126 self.profile = self.extra_args[0]
127 127
128 128 def start(self):
129 129 print self.profile_dir.location
130 130
131 131
132 132 class ProfileList(Application):
133 133 name = u'ipython-profile'
134 134 description = list_help
135 135 examples = _list_examples
136 136
137 137 aliases = Dict({
138 138 'ipython-dir' : 'ProfileList.ipython_dir',
139 139 'log-level' : 'Application.log_level',
140 140 })
141 141 flags = Dict(dict(
142 142 debug = ({'Application' : {'log_level' : 0}},
143 143 "Set Application.log_level to 0, maximizing log output."
144 144 )
145 145 ))
146 146
147 147 ipython_dir = Unicode(get_ipython_dir(), config=True,
148 148 help="""
149 149 The name of the IPython directory. This directory is used for logging
150 150 configuration (through profiles), history storage, etc. The default
151 151 is usually $HOME/.ipython. This options can also be specified through
152 152 the environment variable IPYTHONDIR.
153 153 """
154 154 )
155 155
156 156
157 157 def _print_profiles(self, profiles):
158 158 """print list of profiles, indented."""
159 159 for profile in profiles:
160 160 print ' %s' % profile
161 161
162 162 def list_profile_dirs(self):
163 163 profiles = list_bundled_profiles()
164 164 if profiles:
165 165 print
166 166 print "Available profiles in IPython:"
167 167 self._print_profiles(profiles)
168 168 print
169 169 print " The first request for a bundled profile will copy it"
170 170 print " into your IPython directory (%s)," % self.ipython_dir
171 171 print " where you can customize it."
172 172
173 173 profiles = list_profiles_in(self.ipython_dir)
174 174 if profiles:
175 175 print
176 176 print "Available profiles in %s:" % self.ipython_dir
177 177 self._print_profiles(profiles)
178 178
179 179 profiles = list_profiles_in(os.getcwdu())
180 180 if profiles:
181 181 print
182 182 print "Available profiles in current directory (%s):" % os.getcwdu()
183 183 self._print_profiles(profiles)
184 184
185 185 print
186 186 print "To use any of the above profiles, start IPython with:"
187 187 print " ipython --profile=<name>"
188 188 print
189 189
190 190 def start(self):
191 191 self.list_profile_dirs()
192 192
193 193
194 194 create_flags = {}
195 195 create_flags.update(base_flags)
196 196 # don't include '--init' flag, which implies running profile create in other apps
197 197 create_flags.pop('init')
198 198 create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}},
199 199 "reset config files in this profile to the defaults.")
200 200 create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}},
201 201 "Include the config files for parallel "
202 202 "computing apps (ipengine, ipcontroller, etc.)")
203 203
204 204
205 205 class ProfileCreate(BaseIPythonApplication):
206 206 name = u'ipython-profile'
207 207 description = create_help
208 208 examples = _create_examples
209 209 auto_create = Bool(True, config=False)
210 210 def _log_format_default(self):
211 211 return "[%(name)s] %(message)s"
212 212
213 213 def _copy_config_files_default(self):
214 214 return True
215 215
216 216 parallel = Bool(False, config=True,
217 217 help="whether to include parallel computing config files")
218 218 def _parallel_changed(self, name, old, new):
219 219 parallel_files = [ 'ipcontroller_config.py',
220 220 'ipengine_config.py',
221 221 'ipcluster_config.py'
222 222 ]
223 223 if new:
224 224 for cf in parallel_files:
225 225 self.config_files.append(cf)
226 226 else:
227 227 for cf in parallel_files:
228 228 if cf in self.config_files:
229 229 self.config_files.remove(cf)
230 230
231 231 def parse_command_line(self, argv):
232 232 super(ProfileCreate, self).parse_command_line(argv)
233 233 # accept positional arg as profile name
234 234 if self.extra_args:
235 235 self.profile = self.extra_args[0]
236 236
237 237 flags = Dict(create_flags)
238 238
239 239 classes = [ProfileDir]
240 240
241 241 def _import_app(self, app_path):
242 242 """import an app class"""
243 243 app = None
244 244 name = app_path.rsplit('.', 1)[-1]
245 245 try:
246 246 app = import_item(app_path)
247 247 except ImportError as e:
248 248 self.log.info("Couldn't import %s, config file will be excluded", name)
249 249 except Exception:
250 250 self.log.warn('Unexpected error importing %s', name, exc_info=True)
251 251 return app
252 252
253 253 def init_config_files(self):
254 254 super(ProfileCreate, self).init_config_files()
255 255 # use local imports, since these classes may import from here
256 256 from IPython.terminal.ipapp import TerminalIPythonApp
257 257 apps = [TerminalIPythonApp]
258 258 for app_path in (
259 259 'IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
260 260 'IPython.html.notebookapp.NotebookApp',
261 261 'IPython.nbconvert.nbconvertapp.NbConvertApp',
262 262 ):
263 263 app = self._import_app(app_path)
264 264 if app is not None:
265 265 apps.append(app)
266 266 if self.parallel:
267 267 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
268 268 from IPython.parallel.apps.ipengineapp import IPEngineApp
269 269 from IPython.parallel.apps.ipclusterapp import IPClusterStart
270 270 from IPython.parallel.apps.iploggerapp import IPLoggerApp
271 271 apps.extend([
272 272 IPControllerApp,
273 273 IPEngineApp,
274 274 IPClusterStart,
275 275 IPLoggerApp,
276 276 ])
277 277 for App in apps:
278 278 app = App()
279 279 app.config.update(self.config)
280 280 app.log = self.log
281 281 app.overwrite = self.overwrite
282 282 app.copy_config_files=True
283 app.profile = self.profile
284 app.init_profile_dir()
283 app.ipython_dir=self.ipython_dir
284 app.profile_dir=self.profile_dir
285 285 app.init_config_files()
286 286
287 287 def stage_default_config_file(self):
288 288 pass
289 289
290 290
291 291 class ProfileApp(Application):
292 292 name = u'ipython-profile'
293 293 description = profile_help
294 294 examples = _main_examples
295 295
296 296 subcommands = Dict(dict(
297 297 create = (ProfileCreate, ProfileCreate.description.splitlines()[0]),
298 298 list = (ProfileList, ProfileList.description.splitlines()[0]),
299 299 ))
300 300
301 301 def start(self):
302 302 if self.subapp is None:
303 303 print "No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())
304 304 print
305 305 self.print_description()
306 306 self.print_subcommands()
307 307 self.exit(1)
308 308 else:
309 309 return self.subapp.start()
310 310
@@ -1,153 +1,166 b''
1 1 # coding: utf-8
2 2 """Tests for profile-related functions.
3 3
4 4 Currently only the startup-dir functionality is tested, but more tests should
5 5 be added for:
6 6
7 7 * ipython profile create
8 8 * ipython profile list
9 9 * ipython profile create --parallel
10 10 * security dir permissions
11 11
12 12 Authors
13 13 -------
14 14
15 15 * MinRK
16 16
17 17 """
18 18 from __future__ import absolute_import
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Imports
22 22 #-----------------------------------------------------------------------------
23 23
24 24 import os
25 25 import shutil
26 26 import sys
27 27 import tempfile
28 28
29 29 from unittest import TestCase
30 30
31 31 import nose.tools as nt
32 32
33 33 from IPython.core.profileapp import list_profiles_in, list_bundled_profiles
34 34 from IPython.core.profiledir import ProfileDir
35 35
36 36 from IPython.testing import decorators as dec
37 37 from IPython.testing import tools as tt
38 38 from IPython.utils import py3compat
39
39 from IPython.utils.process import getoutput
40 from IPython.utils.tempdir import TemporaryDirectory
40 41
41 42 #-----------------------------------------------------------------------------
42 43 # Globals
43 44 #-----------------------------------------------------------------------------
44 45 TMP_TEST_DIR = tempfile.mkdtemp()
45 46 HOME_TEST_DIR = os.path.join(TMP_TEST_DIR, "home_test_dir")
46 47 IP_TEST_DIR = os.path.join(HOME_TEST_DIR,'.ipython')
47 48
48 49 #
49 50 # Setup/teardown functions/decorators
50 51 #
51 52
52 53 def setup():
53 54 """Setup test environment for the module:
54 55
55 56 - Adds dummy home dir tree
56 57 """
57 58 # Do not mask exceptions here. In particular, catching WindowsError is a
58 59 # problem because that exception is only defined on Windows...
59 60 os.makedirs(IP_TEST_DIR)
60 61
61 62
62 63 def teardown():
63 64 """Teardown test environment for the module:
64 65
65 66 - Remove dummy home dir tree
66 67 """
67 68 # Note: we remove the parent test dir, which is the root of all test
68 69 # subdirs we may have created. Use shutil instead of os.removedirs, so
69 70 # that non-empty directories are all recursively removed.
70 71 shutil.rmtree(TMP_TEST_DIR)
71 72
72 73
73 74 #-----------------------------------------------------------------------------
74 75 # Test functions
75 76 #-----------------------------------------------------------------------------
76 77 def win32_without_pywin32():
77 78 if sys.platform == 'win32':
78 79 try:
79 80 import pywin32
80 81 except ImportError:
81 82 return True
82 83 return False
83 84
84 85
85 86 class ProfileStartupTest(TestCase):
86 87 def setUp(self):
87 88 # create profile dir
88 89 self.pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, 'test')
89 90 self.options = ['--ipython-dir', IP_TEST_DIR, '--profile', 'test']
90 91 self.fname = os.path.join(TMP_TEST_DIR, 'test.py')
91 92
92 93 def tearDown(self):
93 94 # We must remove this profile right away so its presence doesn't
94 95 # confuse other tests.
95 96 shutil.rmtree(self.pd.location)
96 97
97 98 def init(self, startup_file, startup, test):
98 99 # write startup python file
99 100 with open(os.path.join(self.pd.startup_dir, startup_file), 'w') as f:
100 101 f.write(startup)
101 102 # write simple test file, to check that the startup file was run
102 103 with open(self.fname, 'w') as f:
103 104 f.write(py3compat.doctest_refactor_print(test))
104 105
105 106 def validate(self, output):
106 107 tt.ipexec_validate(self.fname, output, '', options=self.options)
107 108
108 109 @dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows")
109 110 def test_startup_py(self):
110 111 self.init('00-start.py', 'zzz=123\n',
111 112 py3compat.doctest_refactor_print('print zzz\n'))
112 113 self.validate('123')
113 114
114 115 @dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows")
115 116 def test_startup_ipy(self):
116 117 self.init('00-start.ipy', '%profile\n', '')
117 118 self.validate('test')
118 119
119 120
120 121 def test_list_profiles_in():
121 122 # No need to remove these directories and files, as they will get nuked in
122 123 # the module-level teardown.
123 124 td = tempfile.mkdtemp(dir=TMP_TEST_DIR)
124 125 td = py3compat.str_to_unicode(td)
125 126 for name in ('profile_foo', 'profile_hello', 'not_a_profile'):
126 127 os.mkdir(os.path.join(td, name))
127 128 if dec.unicode_paths:
128 129 os.mkdir(os.path.join(td, u'profile_ünicode'))
129 130
130 131 with open(os.path.join(td, 'profile_file'), 'w') as f:
131 132 f.write("I am not a profile directory")
132 133 profiles = list_profiles_in(td)
133 134
134 135 # unicode normalization can turn u'ünicode' into u'u\0308nicode',
135 136 # so only check for *nicode, and that creating a ProfileDir from the
136 137 # name remains valid
137 138 found_unicode = False
138 139 for p in list(profiles):
139 140 if p.endswith('nicode'):
140 141 pd = ProfileDir.find_profile_dir_by_name(td, p)
141 142 profiles.remove(p)
142 143 found_unicode = True
143 144 break
144 145 if dec.unicode_paths:
145 146 nt.assert_true(found_unicode)
146 147 nt.assert_equal(set(profiles), set(['foo', 'hello']))
147 148
148 149
149 150 def test_list_bundled_profiles():
150 151 # This variable will need to be updated when a new profile gets bundled
151 152 bundled_true = [u'cluster', u'math', u'pysh', u'sympy']
152 153 bundled = sorted(list_bundled_profiles())
153 154 nt.assert_equal(bundled, bundled_true)
155
156
157 def test_profile_create_ipython_dir():
158 """ipython profile create respects --ipython-dir"""
159 with TemporaryDirectory() as td:
160 getoutput([sys.executable, '-m', 'IPython', 'profile', 'create',
161 'foo', '--ipython-dir=%s' % td])
162 profile_dir = os.path.join(td, 'profile_foo')
163 assert os.path.exists(profile_dir)
164 ipython_config = os.path.join(profile_dir, 'ipython_config.py')
165 assert os.path.exists(ipython_config)
166 No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now