##// END OF EJS Templates
`ipython profile create` respects `--ipython-dir`...
MinRK -
Show More
@@ -1,315 +1,315 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 from __future__ import print_function
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Copyright (C) 2008 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-----------------------------------------------------------------------------
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Imports
23 23 #-----------------------------------------------------------------------------
24 24
25 25 import os
26 26
27 27 from IPython.config.application import Application
28 28 from IPython.core.application import (
29 29 BaseIPythonApplication, base_flags
30 30 )
31 31 from IPython.core.profiledir import ProfileDir
32 32 from IPython.utils.importstring import import_item
33 33 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
34 34 from IPython.utils import py3compat
35 35 from IPython.utils.traitlets import Unicode, Bool, Dict
36 36
37 37 #-----------------------------------------------------------------------------
38 38 # Constants
39 39 #-----------------------------------------------------------------------------
40 40
41 41 create_help = """Create an IPython profile by name
42 42
43 43 Create an ipython profile directory by its name or
44 44 profile directory path. Profile directories contain
45 45 configuration, log and security related files and are named
46 46 using the convention 'profile_<name>'. By default they are
47 47 located in your ipython directory. Once created, you will
48 48 can edit the configuration files in the profile
49 49 directory to configure IPython. Most users will create a
50 50 profile directory by name,
51 51 `ipython profile create myprofile`, which will put the directory
52 52 in `<ipython_dir>/profile_myprofile`.
53 53 """
54 54 list_help = """List available IPython profiles
55 55
56 56 List all available profiles, by profile location, that can
57 57 be found in the current working directly or in the ipython
58 58 directory. Profile directories are named using the convention
59 59 'profile_<profile>'.
60 60 """
61 61 profile_help = """Manage IPython profiles
62 62
63 63 Profile directories contain
64 64 configuration, log and security related files and are named
65 65 using the convention 'profile_<name>'. By default they are
66 66 located in your ipython directory. You can create profiles
67 67 with `ipython profile create <name>`, or see the profiles you
68 68 already have with `ipython profile list`
69 69
70 70 To get started configuring IPython, simply do:
71 71
72 72 $> ipython profile create
73 73
74 74 and IPython will create the default profile in <ipython_dir>/profile_default,
75 75 where you can edit ipython_config.py to start configuring IPython.
76 76
77 77 """
78 78
79 79 _list_examples = "ipython profile list # list all profiles"
80 80
81 81 _create_examples = """
82 82 ipython profile create foo # create profile foo w/ default config files
83 83 ipython profile create foo --reset # restage default config files over current
84 84 ipython profile create foo --parallel # also stage parallel config files
85 85 """
86 86
87 87 _main_examples = """
88 88 ipython profile create -h # show the help string for the create subcommand
89 89 ipython profile list -h # show the help string for the list subcommand
90 90
91 91 ipython locate profile foo # print the path to the directory for profile 'foo'
92 92 """
93 93
94 94 #-----------------------------------------------------------------------------
95 95 # Profile Application Class (for `ipython profile` subcommand)
96 96 #-----------------------------------------------------------------------------
97 97
98 98
99 99 def list_profiles_in(path):
100 100 """list profiles in a given root directory"""
101 101 files = os.listdir(path)
102 102 profiles = []
103 103 for f in files:
104 104 try:
105 105 full_path = os.path.join(path, f)
106 106 except UnicodeError:
107 107 continue
108 108 if os.path.isdir(full_path) and f.startswith('profile_'):
109 109 profiles.append(f.split('_',1)[-1])
110 110 return profiles
111 111
112 112
113 113 def list_bundled_profiles():
114 114 """list profiles that are bundled with IPython."""
115 115 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
116 116 files = os.listdir(path)
117 117 profiles = []
118 118 for profile in files:
119 119 full_path = os.path.join(path, profile)
120 120 if os.path.isdir(full_path) and profile != "__pycache__":
121 121 profiles.append(profile)
122 122 return profiles
123 123
124 124
125 125 class ProfileLocate(BaseIPythonApplication):
126 126 description = """print the path to an IPython profile dir"""
127 127
128 128 def parse_command_line(self, argv=None):
129 129 super(ProfileLocate, self).parse_command_line(argv)
130 130 if self.extra_args:
131 131 self.profile = self.extra_args[0]
132 132
133 133 def start(self):
134 134 print(self.profile_dir.location)
135 135
136 136
137 137 class ProfileList(Application):
138 138 name = u'ipython-profile'
139 139 description = list_help
140 140 examples = _list_examples
141 141
142 142 aliases = Dict({
143 143 'ipython-dir' : 'ProfileList.ipython_dir',
144 144 'log-level' : 'Application.log_level',
145 145 })
146 146 flags = Dict(dict(
147 147 debug = ({'Application' : {'log_level' : 0}},
148 148 "Set Application.log_level to 0, maximizing log output."
149 149 )
150 150 ))
151 151
152 152 ipython_dir = Unicode(get_ipython_dir(), config=True,
153 153 help="""
154 154 The name of the IPython directory. This directory is used for logging
155 155 configuration (through profiles), history storage, etc. The default
156 156 is usually $HOME/.ipython. This options can also be specified through
157 157 the environment variable IPYTHONDIR.
158 158 """
159 159 )
160 160
161 161
162 162 def _print_profiles(self, profiles):
163 163 """print list of profiles, indented."""
164 164 for profile in profiles:
165 165 print(' %s' % profile)
166 166
167 167 def list_profile_dirs(self):
168 168 profiles = list_bundled_profiles()
169 169 if profiles:
170 170 print()
171 171 print("Available profiles in IPython:")
172 172 self._print_profiles(profiles)
173 173 print()
174 174 print(" The first request for a bundled profile will copy it")
175 175 print(" into your IPython directory (%s)," % self.ipython_dir)
176 176 print(" where you can customize it.")
177 177
178 178 profiles = list_profiles_in(self.ipython_dir)
179 179 if profiles:
180 180 print()
181 181 print("Available profiles in %s:" % self.ipython_dir)
182 182 self._print_profiles(profiles)
183 183
184 184 profiles = list_profiles_in(py3compat.getcwd())
185 185 if profiles:
186 186 print()
187 187 print("Available profiles in current directory (%s):" % py3compat.getcwd())
188 188 self._print_profiles(profiles)
189 189
190 190 print()
191 191 print("To use any of the above profiles, start IPython with:")
192 192 print(" ipython --profile=<name>")
193 193 print()
194 194
195 195 def start(self):
196 196 self.list_profile_dirs()
197 197
198 198
199 199 create_flags = {}
200 200 create_flags.update(base_flags)
201 201 # don't include '--init' flag, which implies running profile create in other apps
202 202 create_flags.pop('init')
203 203 create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}},
204 204 "reset config files in this profile to the defaults.")
205 205 create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}},
206 206 "Include the config files for parallel "
207 207 "computing apps (ipengine, ipcontroller, etc.)")
208 208
209 209
210 210 class ProfileCreate(BaseIPythonApplication):
211 211 name = u'ipython-profile'
212 212 description = create_help
213 213 examples = _create_examples
214 214 auto_create = Bool(True, config=False)
215 215 def _log_format_default(self):
216 216 return "[%(name)s] %(message)s"
217 217
218 218 def _copy_config_files_default(self):
219 219 return True
220 220
221 221 parallel = Bool(False, config=True,
222 222 help="whether to include parallel computing config files")
223 223 def _parallel_changed(self, name, old, new):
224 224 parallel_files = [ 'ipcontroller_config.py',
225 225 'ipengine_config.py',
226 226 'ipcluster_config.py'
227 227 ]
228 228 if new:
229 229 for cf in parallel_files:
230 230 self.config_files.append(cf)
231 231 else:
232 232 for cf in parallel_files:
233 233 if cf in self.config_files:
234 234 self.config_files.remove(cf)
235 235
236 236 def parse_command_line(self, argv):
237 237 super(ProfileCreate, self).parse_command_line(argv)
238 238 # accept positional arg as profile name
239 239 if self.extra_args:
240 240 self.profile = self.extra_args[0]
241 241
242 242 flags = Dict(create_flags)
243 243
244 244 classes = [ProfileDir]
245 245
246 246 def _import_app(self, app_path):
247 247 """import an app class"""
248 248 app = None
249 249 name = app_path.rsplit('.', 1)[-1]
250 250 try:
251 251 app = import_item(app_path)
252 252 except ImportError:
253 253 self.log.info("Couldn't import %s, config file will be excluded", name)
254 254 except Exception:
255 255 self.log.warn('Unexpected error importing %s', name, exc_info=True)
256 256 return app
257 257
258 258 def init_config_files(self):
259 259 super(ProfileCreate, self).init_config_files()
260 260 # use local imports, since these classes may import from here
261 261 from IPython.terminal.ipapp import TerminalIPythonApp
262 262 apps = [TerminalIPythonApp]
263 263 for app_path in (
264 264 'IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
265 265 'IPython.html.notebookapp.NotebookApp',
266 266 'IPython.nbconvert.nbconvertapp.NbConvertApp',
267 267 ):
268 268 app = self._import_app(app_path)
269 269 if app is not None:
270 270 apps.append(app)
271 271 if self.parallel:
272 272 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
273 273 from IPython.parallel.apps.ipengineapp import IPEngineApp
274 274 from IPython.parallel.apps.ipclusterapp import IPClusterStart
275 275 from IPython.parallel.apps.iploggerapp import IPLoggerApp
276 276 apps.extend([
277 277 IPControllerApp,
278 278 IPEngineApp,
279 279 IPClusterStart,
280 280 IPLoggerApp,
281 281 ])
282 282 for App in apps:
283 283 app = App()
284 284 app.config.update(self.config)
285 285 app.log = self.log
286 286 app.overwrite = self.overwrite
287 287 app.copy_config_files=True
288 app.profile = self.profile
289 app.init_profile_dir()
288 app.ipython_dir=self.ipython_dir
289 app.profile_dir=self.profile_dir
290 290 app.init_config_files()
291 291
292 292 def stage_default_config_file(self):
293 293 pass
294 294
295 295
296 296 class ProfileApp(Application):
297 297 name = u'ipython-profile'
298 298 description = profile_help
299 299 examples = _main_examples
300 300
301 301 subcommands = Dict(dict(
302 302 create = (ProfileCreate, ProfileCreate.description.splitlines()[0]),
303 303 list = (ProfileList, ProfileList.description.splitlines()[0]),
304 304 locate = (ProfileLocate, ProfileLocate.description.splitlines()[0]),
305 305 ))
306 306
307 307 def start(self):
308 308 if self.subapp is None:
309 309 print("No subcommand specified. Must specify one of: %s"%(self.subcommands.keys()))
310 310 print()
311 311 self.print_description()
312 312 self.print_subcommands()
313 313 self.exit(1)
314 314 else:
315 315 return self.subapp.start()
@@ -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', '%xmode plain\n', '')
117 118 self.validate('Exception reporting mode: Plain')
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