Repository: ipython/ipython

Issue:
frozen setup: permission denied for default ipython_dir
Ipython's embedded shell is creating its configuration directory in `../.ipython` in frozen setups. When the directory is not writeable (such as if the frozen code was in `c:/Program\ Files/app/`, then `c:/Program\ Files/.ipython` fails to be created by non-admin), ipython fails with `WindowsError`. This case should be handled more gracefully - e.g. by doing without configuration directory.

A workaround is to set IPYTHONDIR to something writeable (in my case, `expanduser('~/.ipython')`) before constructing the embedded shell. 



Discussion:
Thanks for the report. Can you provide quick instructions to repeat this? In particular, which freezing tool are you using? And can you track down the code that picks that directory?

I am using www.pyinstaller.org for the freeze. The code in woo.qt which causes the problem is

```
from IPython.frontend.terminal.embed import InteractiveShellEmbed
from IPython.config.configurable import MultipleInstanceError
try: InteractiveShellEmbed.instance() 
except MultipleInstanceError: print 'Already in ipython, not embedding new instance'
```

The traceback is as follows:

```
C:\Program Files (x86)\Woo> wwoo.exe
[....]
Traceback (most recent call last):
  File "<string>", line 2, in <module>
  File "c:\src\woo\build\pyi.win32\woo.pyinstaller\out00-PYZ.pyz\wooMain", line 231, in main
  File "c:\src\pyinstaller-develop\PyInstaller\loader\pyi_iu.py", line 386, in importHook
  File "c:\src\pyinstaller-develop\PyInstaller\loader\pyi_iu.py", line 480, in doimport
  File "c:\src\woo\build\pyi.win32\woo.pyinstaller\out00-PYZ.pyz\woo.qt", line 64, in <module>
  File "c:\src\woo\build\pyi.win32\woo.pyinstaller\out00-PYZ.pyz\IPython.config.configurable", line 318, in instance
  File "c:\src\woo\build\pyi.win32\woo.pyinstaller\out00-PYZ.pyz\IPython.frontend.terminal.embed", line 97, in __init__
  File "c:\src\woo\build\pyi.win32\woo.pyinstaller\out00-PYZ.pyz\IPython.frontend.terminal.interactiveshell", line 360, in __init__
  File "c:\src\woo\build\pyi.win32\woo.pyinstaller\out00-PYZ.pyz\IPython.core.interactiveshell", line 412, in __init__
  File "c:\src\woo\build\pyi.win32\woo.pyinstaller\out00-PYZ.pyz\IPython.core.interactiveshell", line 522, in init_ipython_dir
  File "c:\src\woo\build\pyi.win32\woo.pyinstaller\out00-PYZ.pyz\IPython.utils.traitlets", line 307, in __set__
  File "c:\src\woo\build\pyi.win32\woo.pyinstaller\out00-PYZ.pyz\IPython.utils.traitlets", line 456, in _notify_trait
  File "c:\src\woo\build\pyi.win32\woo.pyinstaller\out00-PYZ.pyz\IPython.core.interactiveshell", line 496, in _ipython_dir_changed
  File "c:\src\woo\build\pyi.win32\woo.pyinstaller\out00-PYZ.pyz\os", line 157, in makedirs
WindowsError: [Error 5] Access is denied: u'c:\\Program Files (x86)\\.ipython'
```

It looks like the relevant code is here: https://github.com/ipython/ipython/blob/master/IPython/utils/path.py#L172

It seems to be doing that deliberately, although I'm not sure why. @fperez , @ellisonbg , @minrk : does anyone remember why `get_home_dir()` has a special case for frozen executables?

Nope - I've never understood that part.  Anyone with better long-term memory?

If nobody can come up with a still-valid case for it, I say we remove it, and wait for it to come up again.

One can search the history using git pickaxe (i.e., `git log -SIPYKIT`) but
nothing enlightening came up.

@bfroehle yes, I think we've moved VCS systems twice since that code came in. We've already found that several of the special cases that had crept into get_home_dir had become inappropriate, so I won't be surprised if this did as well.

Ah, IpyKit rings a bell. It was a frozen copy of IPython for Windows users, from about 5 years ago. You can still download it. Digging into the source suggests that it's done like that so that it can use config files shipped in the package.

I do know someone who still freezes IPython, I'll just check with him whether he relies on this behaviour.

My wild guess is that there was conflation of _frozen_ and _portable_ (no-install, runnable from removable media without writing to other FS) setup.

@takluyver did you come up with anything here?

Oh, yes, Almar (who freezes IPython for Pyzo) says he doesn't rely on this feature, so I think we can safely remove the special case for running frozen.


Resolving patch:
diff --git a/IPython/utils/path.py b/IPython/utils/path.py
--- a/IPython/utils/path.py
+++ b/IPython/utils/path.py
@@ -172,8 +172,7 @@ class HomeDirError(Exception):
 def get_home_dir(require_writable=False):
     """Return the 'home' directory, as a unicode string.
 
-    * First, check for frozen env in case of py2exe
-    * Otherwise, defer to os.path.expanduser('~')
+    Uses os.path.expanduser('~'), and checks for writability.
     
     See stdlib docs for how this is determined.
     $HOME is first priority on *ALL* platforms.
@@ -189,19 +188,6 @@ def get_home_dir(require_writable=False):
             The path is resolved, but it is not guaranteed to exist or be writable.
     """
 
-    # first, check py2exe distribution root directory for _ipython.
-    # This overrides all. Normally does not exist.
-
-    if hasattr(sys, "frozen"): #Is frozen by py2exe
-        if '\\library.zip\\' in IPython.__file__.lower():#libraries compressed to zip-file
-            root, rest = IPython.__file__.lower().split('library.zip')
-        else:
-            root=os.path.join(os.path.split(IPython.__file__)[0],"../../")
-        root=os.path.abspath(root).rstrip('\\')
-        if _writable_dir(os.path.join(root, '_ipython')):
-            os.environ["IPYKITROOT"] = root
-        return py3compat.cast_unicode(root, fs_encoding)
-    
     homedir = os.path.expanduser('~')
     # Next line will make things work even when /home/ is a symlink to
     # /usr/home as it is on FreeBSD, for example


Test patch:
diff --git a/IPython/utils/tests/test_path.py b/IPython/utils/tests/test_path.py
--- a/IPython/utils/tests/test_path.py
+++ b/IPython/utils/tests/test_path.py
@@ -103,7 +103,7 @@ def setup_environment():
 
 
 def teardown_environment():
-    """Restore things that were remebered by the setup_environment function
+    """Restore things that were remembered by the setup_environment function
     """
     (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
     os.chdir(old_wd)
@@ -126,13 +126,14 @@ def teardown_environment():
 def test_get_home_dir_1():
     """Testcase for py2exe logic, un-compressed lib
     """
+    unfrozen = path.get_home_dir()
     sys.frozen = True
 
     #fake filename for IPython.__init__
     IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
 
     home_dir = path.get_home_dir()
-    nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
+    nt.assert_equal(home_dir, unfrozen)
 
 
 @skip_if_not_win32
@@ -140,12 +141,13 @@ def test_get_home_dir_1():
 def test_get_home_dir_2():
     """Testcase for py2exe logic, compressed lib
     """
+    unfrozen = path.get_home_dir()
     sys.frozen = True
     #fake filename for IPython.__init__
     IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
 
     home_dir = path.get_home_dir(True)
-    nt.assert_equal(home_dir, abspath(HOME_TEST_DIR).lower())
+    nt.assert_equal(home_dir, unfrozen)
 
 
 @with_environment