From b131150392b48af796f683a5fd5f03213cd942a4 2011-10-31 00:35:15 From: Fernando Perez Date: 2011-10-31 00:35:15 Subject: [PATCH] Merge pull request #950 from minrk/startup Add directory for startup files, enabling users to add local configuration more simply without having to know how the config system works much at all. Now, `profile/startup` dir is checked for files, and they are run prior to explicit exec_code and exec_files. A short README is added to the dir explaining how they work, and a short paragraph is added to the docs, in both the interactive tutorial and config overview. --- diff --git a/IPython/config/profile/README_STARTUP b/IPython/config/profile/README_STARTUP new file mode 100644 index 0000000..61d4700 --- /dev/null +++ b/IPython/config/profile/README_STARTUP @@ -0,0 +1,11 @@ +This is the IPython startup directory + +.py and .ipy files in this directory will be run *prior* to any code or files specified +via the exec_lines or exec_files configurables whenever you load this profile. + +Files will be run in lexicographical order, so you can control the execution order of files +with a prefix, e.g.:: + + 00-first.py + 50-middle.py + 99-last.ipy diff --git a/IPython/core/profiledir.py b/IPython/core/profiledir.py index 25ad5b4..935dbf9 100644 --- a/IPython/core/profiledir.py +++ b/IPython/core/profiledir.py @@ -59,9 +59,11 @@ class ProfileDir(LoggingConfigurable): security_dir_name = Unicode('security') log_dir_name = Unicode('log') + startup_dir_name = Unicode('startup') pid_dir_name = Unicode('pid') security_dir = Unicode(u'') log_dir = Unicode(u'') + startup_dir = Unicode(u'') pid_dir = Unicode(u'') location = Unicode(u'', config=True, @@ -81,6 +83,7 @@ class ProfileDir(LoggingConfigurable): # ensure config files exist: self.security_dir = os.path.join(new, self.security_dir_name) self.log_dir = os.path.join(new, self.log_dir_name) + self.startup_dir = os.path.join(new, self.startup_dir_name) self.pid_dir = os.path.join(new, self.pid_dir_name) self.check_dirs() @@ -91,6 +94,17 @@ class ProfileDir(LoggingConfigurable): if not os.path.isdir(self.log_dir): os.mkdir(self.log_dir) + def _startup_dir_changed(self, name, old, new): + self.check_startup_dir() + + def check_startup_dir(self): + if not os.path.isdir(self.startup_dir): + os.mkdir(self.startup_dir) + readme = os.path.join(self.startup_dir, 'README') + src = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'README_STARTUP') + if not os.path.exists(readme): + shutil.copy(src, readme) + def _security_dir_changed(self, name, old, new): self.check_security_dir() @@ -119,6 +133,7 @@ class ProfileDir(LoggingConfigurable): self.check_security_dir() self.check_log_dir() self.check_pid_dir() + self.check_startup_dir() def copy_config_file(self, config_file, path=None, overwrite=False): """Copy a default config file into the active profile directory. diff --git a/IPython/core/shellapp.py b/IPython/core/shellapp.py index 95a115f..6549c24 100644 --- a/IPython/core/shellapp.py +++ b/IPython/core/shellapp.py @@ -22,6 +22,7 @@ Authors from __future__ import absolute_import +import glob import os import sys @@ -175,6 +176,7 @@ class InteractiveShellApp(Configurable): def init_code(self): """run the pre-flight code, specified via exec_lines""" + self._run_startup_files() self._run_exec_lines() self._run_exec_files() self._run_cmd_line_code() @@ -230,6 +232,22 @@ class InteractiveShellApp(Configurable): finally: sys.argv = save_argv + def _run_startup_files(self): + """Run files from profile startup directory""" + startup_dir = self.profile_dir.startup_dir + startup_files = glob.glob(os.path.join(startup_dir, '*.py')) + startup_files += glob.glob(os.path.join(startup_dir, '*.ipy')) + if not startup_files: + return + + self.log.debug("Running startup files from %s...", startup_dir) + try: + for fname in sorted(startup_files): + self._exec_file(fname) + except: + self.log.warn("Unknown error in handling startup files:") + self.shell.showtraceback() + def _run_exec_files(self): """Run files from IPythonApp.exec_files""" if not self.exec_files: diff --git a/IPython/core/tests/test_profile.py b/IPython/core/tests/test_profile.py new file mode 100644 index 0000000..edba003 --- /dev/null +++ b/IPython/core/tests/test_profile.py @@ -0,0 +1,103 @@ +"""Tests for profile-related functions. + +Currently only the startup-dir functionality is tested, but more tests should +be added for: + + * ipython profile create + * ipython profile list + * ipython profile create --parallel + * security dir permissions + +Authors +------- + +* MinRK + +""" +from __future__ import absolute_import + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import os +import shutil +import sys +import tempfile + +import nose.tools as nt +from nose import SkipTest + +from IPython.core.profiledir import ProfileDir + +from IPython.testing import decorators as dec +from IPython.testing import tools as tt +from IPython.utils import py3compat + + +#----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- +TMP_TEST_DIR = tempfile.mkdtemp() +HOME_TEST_DIR = os.path.join(TMP_TEST_DIR, "home_test_dir") +IP_TEST_DIR = os.path.join(HOME_TEST_DIR,'.ipython') + +# +# Setup/teardown functions/decorators +# + +def setup(): + """Setup test environment for the module: + + - Adds dummy home dir tree + """ + # Do not mask exceptions here. In particular, catching WindowsError is a + # problem because that exception is only defined on Windows... + os.makedirs(IP_TEST_DIR) + + +def teardown(): + """Teardown test environment for the module: + + - Remove dummy home dir tree + """ + # Note: we remove the parent test dir, which is the root of all test + # subdirs we may have created. Use shutil instead of os.removedirs, so + # that non-empty directories are all recursively removed. + shutil.rmtree(TMP_TEST_DIR) + + +#----------------------------------------------------------------------------- +# Test functions +#----------------------------------------------------------------------------- + +def test_startup_py(): + # create profile dir + pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, 'test') + # write startup python file + with open(os.path.join(pd.startup_dir, '00-start.py'), 'w') as f: + f.write('zzz=123\n') + # write simple test file, to check that the startup file was run + fname = os.path.join(TMP_TEST_DIR, 'test.py') + with open(fname, 'w') as f: + f.write('print zzz\n') + # validate output + tt.ipexec_validate(fname, '123', '', + options=['--ipython-dir', IP_TEST_DIR, '--profile', 'test']) + +def test_startup_ipy(): + # create profile dir + pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, 'test') + # write startup ipython file + with open(os.path.join(pd.startup_dir, '00-start.ipy'), 'w') as f: + f.write('%profile\n') + # write empty script, because we don't need anything to happen + # after the startup file is run + fname = os.path.join(TMP_TEST_DIR, 'test.py') + with open(fname, 'w') as f: + f.write('') + # validate output + tt.ipexec_validate(fname, 'test', '', + options=['--ipython-dir', IP_TEST_DIR, '--profile', 'test']) + + \ No newline at end of file diff --git a/IPython/testing/tools.py b/IPython/testing/tools.py index 053ffba..fa0b7e4 100644 --- a/IPython/testing/tools.py +++ b/IPython/testing/tools.py @@ -259,7 +259,7 @@ def ipexec_validate(fname, expected_out, expected_err='', import nose.tools as nt - out, err = ipexec(fname) + out, err = ipexec(fname, options) #print 'OUT', out # dbg #print 'ERR', err # dbg # If there are any errors, we must check those befor stdout, as they may be diff --git a/docs/source/config/overview.txt b/docs/source/config/overview.txt index 3717de3..8b7a9e5 100644 --- a/docs/source/config/overview.txt +++ b/docs/source/config/overview.txt @@ -358,6 +358,35 @@ you create profiles with the name of one of our shipped profiles, these config files will be copied over instead of starting with the automatically generated config files. +Security Files +-------------- + +If you are using the notebook, qtconsole, or parallel code, IPython stores +connection information in small JSON files in the active profile's security +directory. This directory is made private, so only you can see the files inside. If +you need to move connection files around to other computers, this is where they will +be. If you want your code to be able to open security files by name, we have a +convenience function :func:`IPython.utils.path.get_security_file`, which will return +the absolute path to a security file from its filename and [optionally] profile +name. + +Startup Files +------------- + +If you want some code to be run at the beginning of every IPython session with a +particular profile, the easiest way is to add Python (.py) or IPython (.ipy) scripts +to your :file:`/startup` directory. Files in this directory will always be +executed as soon as the IPython shell is constructed, and before any other code or +scripts you have specified. If you have multiple files in the startup directory, +they will be run in lexicographical order, so you can control the ordering by adding +a '00-' prefix. + +.. note:: + + Automatic startup files are new in IPython 0.12. Use the + InteractiveShellApp.exec_files configurable for similar behavior in 0.11. + + .. _commandline: Command-line arguments diff --git a/docs/source/interactive/tutorial.txt b/docs/source/interactive/tutorial.txt index c32d2cc..01adac8 100644 --- a/docs/source/interactive/tutorial.txt +++ b/docs/source/interactive/tutorial.txt @@ -142,4 +142,19 @@ Profiles allow you to use IPython for different tasks, keeping separate config files and history for each one. More details in :ref:`the profiles section `. +Startup Files +------------- + +If you want some code to be run at the beginning of every IPython session, the +easiest way is to add Python (.py) or IPython (.ipy) scripts to your +:file:`/startup` directory. Files in this directory will always be executed +as soon as the IPython shell is constructed, and before any other code or scripts +you have specified. If you have multiple files in the startup directory, they will +be run in lexicographical order, so you can control the ordering by adding a '00-' +prefix. + +.. note:: + + Automatic startup files are new in IPython 0.12. Use the + InteractiveShellApp.exec_files configurable for similar behavior in 0.11. diff --git a/setupbase.py b/setupbase.py index 0113f6d..8a7efa9 100644 --- a/setupbase.py +++ b/setupbase.py @@ -132,7 +132,7 @@ def find_package_data(): static_data.append(os.path.join(parent, f)) package_data = { - 'IPython.config.profile' : ['README', '*/*.py'], + 'IPython.config.profile' : ['README*', '*/*.py'], 'IPython.testing' : ['*.txt'], 'IPython.frontend.html.notebook' : ['templates/*'] + static_data, 'IPython.frontend.qt.console' : ['resources/icon/*.svg'],