From 15ce9bf5f129636af5da0de1393bab5fe56088a7 2011-02-23 03:00:25 From: MinRK Date: 2011-02-23 03:00:25 Subject: [PATCH] Merge 'XDG', introducing basic XDG_CONFIG_HOME support closes gh-278 closes gh-246 closes gh-48 --- diff --git a/IPython/core/application.py b/IPython/core/application.py index b6c25c6..5ad9432 100644 --- a/IPython/core/application.py +++ b/IPython/core/application.py @@ -105,7 +105,7 @@ class Application(object): default_log_level = logging.WARN #: Set by --profile option profile_name = None - #: User's ipython directory, typically ~/.ipython/ + #: User's ipython directory, typically ~/.ipython or ~/.config/ipython/ ipython_dir = None #: Internal defaults, implemented in code. default_config = None diff --git a/IPython/core/magic.py b/IPython/core/magic.py index 27bac57..4fc67be 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -395,7 +395,7 @@ to 'mydir', if it exists. You can define your own magic functions to extend the system. See the supplied ipythonrc and example-magic.py files for details (in your ipython -configuration directory, typically $HOME/.ipython/). +configuration directory, typically $HOME/.config/ipython on Linux or $HOME/.ipython elsewhere). You can also define your own aliased names for magic functions. In your ipythonrc file, placing a line like: diff --git a/IPython/core/usage.py b/IPython/core/usage.py index 77aa3e2..1864826 100644 --- a/IPython/core/usage.py +++ b/IPython/core/usage.py @@ -30,9 +30,10 @@ IPython: an enhanced interactive Python shell. command line, simply because they are not practical here. Look into your ipython_config.py configuration file for details on those. - This file typically installed in the $HOME/.ipython directory. For Windows - users, $HOME resolves to C:\\Documents and Settings\\YourUserName in most - instances. + This file is typically installed in the IPYTHON_DIR directory. For Linux + users, this will be $HOME/.config/ipython, and for other users it will be + $HOME/.ipython. For Windows users, $HOME resolves to C:\\Documents and + Settings\\YourUserName in most instances. In IPython's documentation, we will refer to this directory as IPYTHON_DIR, you can change its default location by setting any path you want in this diff --git a/IPython/kernel/controllerservice.py b/IPython/kernel/controllerservice.py index e8e30f4..9056aaa 100644 --- a/IPython/kernel/controllerservice.py +++ b/IPython/kernel/controllerservice.py @@ -186,7 +186,7 @@ class ControllerService(object, service.Service): This method takes the assigned id, ip/port and pid of the engine and saves it to a file of the form: - ~/.ipython/log/ipcontroller-###-engine-info.log + IPYTHON_DIR/log/ipcontroller-###-engine-info.log where ### is the pid of the controller. diff --git a/IPython/kernel/ipcontrollerapp.py b/IPython/kernel/ipcontrollerapp.py index 24fb9a9..1e3bba1 100755 --- a/IPython/kernel/ipcontrollerapp.py +++ b/IPython/kernel/ipcontrollerapp.py @@ -49,7 +49,7 @@ The IPython controller provides a gateway between the IPython engines and clients. The controller needs to be started before the engines and can be configured using command line options or using a cluster directory. Cluster directories contain config, log and security files and are usually located in -your .ipython directory and named as "cluster_". See the --profile +your ipython directory and named as "cluster_". See the --profile and --cluster-dir options for details. """ diff --git a/IPython/kernel/ipengineapp.py b/IPython/kernel/ipengineapp.py index 61f010a..9ec6e7a 100755 --- a/IPython/kernel/ipengineapp.py +++ b/IPython/kernel/ipengineapp.py @@ -60,7 +60,7 @@ IPython engines run in parallel and perform computations on behalf of a client and controller. A controller needs to be started before the engines. The engine can be configured using command line options or using a cluster directory. Cluster directories contain config, log and security files and are -usually located in your .ipython directory and named as "cluster_". +usually located in your ipython directory and named as "cluster_". See the --profile and --cluster-dir options for details. """ diff --git a/IPython/utils/path.py b/IPython/utils/path.py index 69cf58f..5d53dbf 100644 --- a/IPython/utils/path.py +++ b/IPython/utils/path.py @@ -246,6 +246,24 @@ def get_home_dir(): else: raise HomeDirError('No valid home directory could be found for your OS') +def get_xdg_dir(): + """Return the XDG_CONFIG_HOME, if it is defined and exists, else None. + + This is only for posix (Linux,Unix,OS X, etc) systems. + """ + + isdir = os.path.isdir + env = os.environ + + if os.name == 'posix': + # Linux, Unix, AIX, OS X + # use ~/.config if not set OR empty + xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config') + if xdg and isdir(xdg): + return xdg.decode(sys.getfilesystemencoding()) + + return None + def get_ipython_dir(): """Get the IPython directory for this platform and user. @@ -253,14 +271,34 @@ def get_ipython_dir(): This uses the logic in `get_home_dir` to find the home directory and the adds .ipython to the end of the path. """ + + env = os.environ + pjoin = os.path.join + exists = os.path.exists + ipdir_def = '.ipython' + xdg_def = 'ipython' + home_dir = get_home_dir() + xdg_dir = get_xdg_dir() # import pdb; pdb.set_trace() # dbg - ipdir = os.environ.get( - 'IPYTHON_DIR', os.environ.get( - 'IPYTHONDIR', os.path.join(home_dir, ipdir_def) - ) - ) + ipdir = env.get('IPYTHON_DIR', env.get('IPYTHONDIR', None)) + if ipdir is None: + # not set explicitly, use XDG_CONFIG_HOME or HOME + home_ipdir = pjoin(home_dir, ipdir_def) + if xdg_dir: + # use XDG, as long as the user isn't already + # using $HOME/.ipython and *not* XDG/ipython + + xdg_ipdir = pjoin(xdg_dir, xdg_def) + + if exists(xdg_ipdir) or not exists(home_ipdir): + ipdir = xdg_ipdir + + if ipdir is None: + # not using XDG + ipdir = home_ipdir + return ipdir.decode(sys.getfilesystemencoding()) diff --git a/IPython/utils/tests/test_path.py b/IPython/utils/tests/test_path.py index db9a8a8..332d4fb 100644 --- a/IPython/utils/tests/test_path.py +++ b/IPython/utils/tests/test_path.py @@ -46,6 +46,7 @@ env = os.environ TEST_FILE_PATH = split(abspath(__file__))[0] TMP_TEST_DIR = tempfile.mkdtemp() HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir") +XDG_TEST_DIR = join(HOME_TEST_DIR, "xdg_test_dir") IP_TEST_DIR = join(HOME_TEST_DIR,'.ipython') # # Setup/teardown functions/decorators @@ -59,6 +60,7 @@ def setup(): # 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) + os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython')) def teardown(): @@ -236,9 +238,93 @@ def test_get_ipython_dir_2(): os.name = "posix" env.pop('IPYTHON_DIR', None) env.pop('IPYTHONDIR', None) + env.pop('XDG_CONFIG_HOME', None) ipdir = path.get_ipython_dir() nt.assert_equal(ipdir, os.path.join("someplace", ".ipython")) +@with_environment +def test_get_ipython_dir_3(): + """test_get_ipython_dir_3, use XDG if defined, and .ipython doesn't exist.""" + path.get_home_dir = lambda : "someplace" + os.name = "posix" + env.pop('IPYTHON_DIR', None) + env.pop('IPYTHONDIR', None) + env['XDG_CONFIG_HOME'] = XDG_TEST_DIR + ipdir = path.get_ipython_dir() + nt.assert_equal(ipdir, os.path.join(XDG_TEST_DIR, "ipython")) + +@with_environment +def test_get_ipython_dir_4(): + """test_get_ipython_dir_4, use XDG if both exist.""" + path.get_home_dir = lambda : HOME_TEST_DIR + os.name = "posix" + env.pop('IPYTHON_DIR', None) + env.pop('IPYTHONDIR', None) + env['XDG_CONFIG_HOME'] = XDG_TEST_DIR + xdg_ipdir = os.path.join(XDG_TEST_DIR, "ipython") + ipdir = path.get_ipython_dir() + nt.assert_equal(ipdir, xdg_ipdir) + +@with_environment +def test_get_ipython_dir_5(): + """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist.""" + os.name = "posix" + env.pop('IPYTHON_DIR', None) + env.pop('IPYTHONDIR', None) + env['XDG_CONFIG_HOME'] = XDG_TEST_DIR + os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython')) + ipdir = path.get_ipython_dir() + nt.assert_equal(ipdir, IP_TEST_DIR) + +@with_environment +def test_get_ipython_dir_6(): + """test_get_ipython_dir_6, use XDG if defined and neither exist.""" + path.get_home_dir = lambda : 'somehome' + path.get_xdg_dir = lambda : 'somexdg' + os.name = "posix" + env.pop('IPYTHON_DIR', None) + env.pop('IPYTHONDIR', None) + xdg_ipdir = os.path.join("somexdg", "ipython") + ipdir = path.get_ipython_dir() + nt.assert_equal(ipdir, xdg_ipdir) + +@with_environment +def test_get_xdg_dir_1(): + """test_get_xdg_dir_1, check xdg_dir""" + reload(path) + path.get_home_dir = lambda : 'somewhere' + os.name = "posix" + env.pop('IPYTHON_DIR', None) + env.pop('IPYTHONDIR', None) + env.pop('XDG_CONFIG_HOME', None) + + nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config')) + + +@with_environment +def test_get_xdg_dir_1(): + """test_get_xdg_dir_1, check nonexistant xdg_dir""" + reload(path) + path.get_home_dir = lambda : HOME_TEST_DIR + os.name = "posix" + env.pop('IPYTHON_DIR', None) + env.pop('IPYTHONDIR', None) + env.pop('XDG_CONFIG_HOME', None) + nt.assert_equal(path.get_xdg_dir(), None) + +@with_environment +def test_get_xdg_dir_2(): + """test_get_xdg_dir_2, check xdg_dir default to ~/.config""" + reload(path) + path.get_home_dir = lambda : HOME_TEST_DIR + os.name = "posix" + env.pop('IPYTHON_DIR', None) + env.pop('IPYTHONDIR', None) + env.pop('XDG_CONFIG_HOME', None) + cfgdir=os.path.join(path.get_home_dir(), '.config') + os.makedirs(cfgdir) + + nt.assert_equal(path.get_xdg_dir(), cfgdir) def test_filefind(): """Various tests for filefind""" diff --git a/docs/source/config/old.txt b/docs/source/config/old.txt index feedfa5..fe6e0bd 100644 --- a/docs/source/config/old.txt +++ b/docs/source/config/old.txt @@ -12,7 +12,8 @@ Outdated configuration information that might still be useful This section will help you set various things in your environment for your IPython sessions to be as efficient as possible. All of IPython's configuration information, along with several example files, is stored -in a directory named by default $HOME/.ipython. You can change this by +in a directory named by default $HOME/.config/ipython if $HOME/.config +exists (Linux), or $HOME/.ipython as a secondary default. You can change this by defining the environment variable IPYTHONDIR, or at runtime with the command line option -ipythondir. @@ -94,7 +95,7 @@ sequences. You can go to a 'no color' mode by typing '%colors NoColor'. You can try using a different terminal emulator program (Emacs users, see below). To permanently set your color preferences, edit the file -$HOME/.ipython/ipythonrc and set the colors option to the desired value. +$IPYTHON_DIR/ipythonrc and set the colors option to the desired value. Object details (types, docstrings, source code, etc.) diff --git a/docs/source/config/overview.txt b/docs/source/config/overview.txt index 70d0626..4f99e7a 100644 --- a/docs/source/config/overview.txt +++ b/docs/source/config/overview.txt @@ -235,6 +235,9 @@ This class hierarchy and configuration file accomplishes the following: configuration file. Because :class:`Foo` is the parent of :class:`Bar` it doesn't know anything about the :attr:`othervalue` attribute. + +.. _ipython_dir: + Configuration file location =========================== @@ -246,11 +249,19 @@ this directory is determined by the following algorithm: * If not, the value returned by :func:`IPython.utils.path.get_ipython_dir` is used. This function will first look at the :envvar:`IPYTHON_DIR` - environment variable and then default to the directory - :file:`$HOME/.ipython`. + environment variable and then default to a platform-specific default. + +On posix systems (Linux, Unix, etc.), IPython respects the ``$XDG_CONFIG_HOME`` +part of the `XDG Base Directory`_ specification. If ``$XDG_CONFIG_HOME`` is +defined and exists ( ``XDG_CONFIG_HOME`` has a default interpretation of +:file:`$HOME/.config`), then IPython's config directory will be located in +:file:`$XDG_CONFIG_HOME/ipython`. If users still have an IPython directory +in :file:`$HOME/.ipython`, then that will be used. in preference to the +system default. For most users, the default value will simply be something like -:file:`$HOME/.ipython`. +:file:`$HOME/.config/ipython` on Linux, or :file:`$HOME/.ipython` +elsewhere. Once the location of the IPython directory has been determined, you need to know what filename to use for the configuration file. The basic idea is that @@ -327,3 +338,5 @@ Here are the main requirements we wanted our configuration system to have: dynamic language and you don't always know everything that needs to be configured when a program starts. + +.. _`XDG Base Directory`: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html diff --git a/docs/source/development/doc_guide.txt b/docs/source/development/doc_guide.txt index d49b102..79bb2aa 100644 --- a/docs/source/development/doc_guide.txt +++ b/docs/source/development/doc_guide.txt @@ -51,7 +51,7 @@ An interactive Python session:: >>> from IPython.utils.path import get_ipython_dir >>> get_ipython_dir() - '/home/fperez/.ipython' + '/home/fperez/.config/ipython' An IPython session: diff --git a/docs/source/interactive/reference.txt b/docs/source/interactive/reference.txt index 584789d..7cf643b 100644 --- a/docs/source/interactive/reference.txt +++ b/docs/source/interactive/reference.txt @@ -27,11 +27,12 @@ file and ignore your configuration setup. Please note that some of the configuration options are not available at the command line, simply because they are not practical here. Look into -your ipythonrc configuration file for details on those. This file -typically installed in the $HOME/.ipython directory. For Windows users, -$HOME resolves to C:\\Documents and Settings\\YourUserName in most -instances. In the rest of this text, we will refer to this directory as -IPYTHON_DIR. +your ipythonrc configuration file for details on those. This file is typically +installed in the IPYTHON_DIR directory. For Linux +users, this will be $HOME/.config/ipython, and for other users it will be +$HOME/.ipython. For Windows users, $HOME resolves to C:\\Documents and +Settings\\YourUserName in most instances. + @@ -218,9 +219,9 @@ All options with a [no] prepended can be specified in negated form include this one and load extra things for particular tasks. For example: - 1. $HOME/.ipython/ipythonrc : load basic things you always want. - 2. $HOME/.ipython/ipythonrc-math : load (1) and basic math-related modules. - 3. $HOME/.ipython/ipythonrc-numeric : load (1) and Numeric and plotting modules. + 1. $IPYTHON_DIR/ipythonrc : load basic things you always want. + 2. $IPYTHON_DIR/ipythonrc-math : load (1) and basic math-related modules. + 3. $IPYTHON_DIR/ipythonrc-numeric : load (1) and Numeric and plotting modules. Since it is possible to create an endless loop by having circular file inclusions, IPython will stop if it reaches 15 diff --git a/docs/source/interactive/shell.txt b/docs/source/interactive/shell.txt index 64cb81c..71d39f9 100644 --- a/docs/source/interactive/shell.txt +++ b/docs/source/interactive/shell.txt @@ -25,7 +25,7 @@ the "pysh" shortcut in start menu. If you want to use the features of sh profile as your defaults (which might be a good idea if you use other profiles a lot of the time but still want the convenience of sh profile), add ``import ipy_profile_sh`` -to your ~/.ipython/ipy_user_conf.py. +to your $IPYTHON_DIR/ipy_user_conf.py. The 'sh' profile is different from the default profile in that: diff --git a/docs/source/parallel/parallel_intro.txt b/docs/source/parallel/parallel_intro.txt index 0b2b984..809c251 100644 --- a/docs/source/parallel/parallel_intro.txt +++ b/docs/source/parallel/parallel_intro.txt @@ -138,13 +138,13 @@ these keys are known as Foolscap URLs, or FURLs, because of the underlying network protocol we are using. As a user, you don't need to know anything about the details of these FURLs, other than that when the controller starts, it saves a set of FURLs to files named :file:`something.furl`. The default -location of these files is the :file:`~./ipython/security` directory. +location of these files is the :file:`$IPYTHON_DIR/cluster_/security` directory. To connect and authenticate to the controller an engine or client simply needs to present an appropriate FURL (that was originally created by the controller) to the controller. Thus, the FURL files need to be copied to a location where the clients and engines can find them. Typically, this is the -:file:`~./ipython/security` directory on the host where the client/engine is +:file:`$IPYTHON_DIR/cluster_/security` directory on the host where the client/engine is running (which could be a different host than the controller). Once the FURL files are copied over, everything should work fine. @@ -212,7 +212,7 @@ everything is working correctly, try the following commands: Remember, a client also needs to present a FURL file to the controller. How does this happen? When a multiengine client is created with no arguments, the client tries to find the corresponding FURL file in the local -:file:`~./ipython/security` directory. If it finds it, you are set. If you +:file:`$IPYTHON_DIR/cluster_/security` directory. If it finds it, you are set. If you have put the FURL file in a different location or it has a different name, create the client like this:: diff --git a/docs/source/parallel/parallel_multiengine.txt b/docs/source/parallel/parallel_multiengine.txt index b39123f..ab05faa 100644 --- a/docs/source/parallel/parallel_multiengine.txt +++ b/docs/source/parallel/parallel_multiengine.txt @@ -37,7 +37,7 @@ and then create a :class:`MultiEngineClient` instance: In [2]: mec = client.MultiEngineClient() This form assumes that the :file:`ipcontroller-mec.furl` is in the -:file:`~./ipython/security` directory on the client's host. If not, the +:file:`$IPYTHON_DIR/cluster_/security` directory on the client's host. If not, the location of the FURL file must be given as an argument to the constructor: diff --git a/docs/source/parallel/parallel_process.txt b/docs/source/parallel/parallel_process.txt index 533a038..06d8940 100644 --- a/docs/source/parallel/parallel_process.txt +++ b/docs/source/parallel/parallel_process.txt @@ -40,14 +40,14 @@ hosts ``host1``-``hostn``. The following steps are then required: At this point, the controller and engines will be connected. By default, the FURL files created by the controller are put into the -:file:`~/.ipython/security` directory. If the engines share a filesystem with +:file:`$IPYTHON_DIR/cluster_/security` directory. If the engines share a filesystem with the controller, step 2 can be skipped as the engines will automatically look at that location. The final step required required to actually use the running controller from a client is to move the FURL files :file:`ipcontroller-mec.furl` and :file:`ipcontroller-tc.furl` from ``host0`` to the host where the clients will -be run. If these file are put into the :file:`~/.ipython/security` directory +be run. If these file are put into the :file:`$IPYTHON_DIR/cluster_/security` directory of the client's host, they will be found automatically. Otherwise, the full path to them has to be passed to the client's constructor. @@ -74,7 +74,7 @@ controller and engines in the following situations: .. note:: Currently :command:`ipcluster` requires that the - :file:`~/.ipython/security` directory live on a shared filesystem that is + :file:`$IPYTHON_DIR/cluster_/security` directory live on a shared filesystem that is seen by both the controller and engines. If you don't have a shared file system you will need to use :command:`ipcontroller` and :command:`ipengine` directly. This constraint can be relaxed if you are @@ -289,7 +289,7 @@ the command:: $ ipengine The engines should start and automatically connect to the controller using the -FURL files in :file:`~./ipython/security`. You are now ready to use the +FURL files in :file:`$IPYTHON_DIR/cluster_/security`. You are now ready to use the controller and engines from IPython. .. warning:: @@ -312,7 +312,7 @@ When the controller and engines are running on different hosts, things are slightly more complicated, but the underlying ideas are the same: 1. Start the controller on a host using :command:`ipcontroller`. -2. Copy :file:`ipcontroller-engine.furl` from :file:`~./ipython/security` on +2. Copy :file:`ipcontroller-engine.furl` from :file:`$IPYTHON_DIR/cluster_/security` on the controller's host to the host where the engines will run. 3. Use :command:`ipengine` on the engine's hosts to start the engines. @@ -320,7 +320,7 @@ The only thing you have to be careful of is to tell :command:`ipengine` where the :file:`ipcontroller-engine.furl` file is located. There are two ways you can do this: -* Put :file:`ipcontroller-engine.furl` in the :file:`~./ipython/security` +* Put :file:`ipcontroller-engine.furl` in the :file:`$IPYTHON_DIR/cluster_/security` directory on the engine's host, where it will be found automatically. * Call :command:`ipengine` with the ``--furl-file=full_path_to_the_file`` flag. @@ -332,7 +332,7 @@ The ``--furl-file`` flag works like this:: .. note:: If the controller's and engine's hosts all have a shared file system - (:file:`~./ipython/security` is the same on all of them), then things + (:file:`$IPYTHON_DIR/cluster_/security` is the same on all of them), then things will just work! Make FURL files persistent @@ -346,7 +346,7 @@ to be able to create the key (or FURL file) once, and then simply use it at any point in the future. This is possible, but before you do this, you **must** remove any old FURL -files in the :file:`~/.ipython/security` directory. +files in the :file:`$IPYTHON_DIR/cluster_/security` directory. .. warning:: @@ -379,7 +379,7 @@ Log files All of the components of IPython have log files associated with them. These log files can be extremely useful in debugging problems with -IPython and can be found in the directory :file:`~/.ipython/log`. Sending +IPython and can be found in the directory :file:`$IPYTHON_DIR/cluster_/log`. Sending the log files to us will often help us to debug any problems. diff --git a/docs/source/parallel/parallel_security.txt b/docs/source/parallel/parallel_security.txt index ac95119..7219179 100644 --- a/docs/source/parallel/parallel_security.txt +++ b/docs/source/parallel/parallel_security.txt @@ -117,7 +117,7 @@ controller creates a number of FURLs for different purposes: to execute. Upon starting, the controller creates these different FURLS and writes them -files in the user-read-only directory :file:`$HOME/.ipython/security`. Thus, +files in the user-read-only directory :file:`$IPYTHON_DIR/cluster_default/security`. Thus, only the user who starts the controller has access to the FURLs. For an IPython client or engine to authenticate with a controller, it must diff --git a/docs/source/parallel/parallel_task.txt b/docs/source/parallel/parallel_task.txt index e88cbd9..f88802e 100644 --- a/docs/source/parallel/parallel_task.txt +++ b/docs/source/parallel/parallel_task.txt @@ -41,7 +41,7 @@ and then create a :class:`TaskClient` instance: In [2]: tc = client.TaskClient() This form assumes that the :file:`ipcontroller-tc.furl` is in the -:file:`~./ipython/security` directory on the client's host. If not, the +:file:`$IPYTHON_DIR/cluster_/security` directory on the client's host. If not, the location of the FURL file must be given as an argument to the constructor: