Repository: spyder-ide/spyder

Issue:
Python interpreter path overwritten in console w/external interpreter on the MacOS standalone version if the `Spyder.app` name is changed 
<!--- **PLEASE READ:** When submitting here, please ensure you've completed the following checklist and checked the boxes to confirm. Issue reports without it may be closed. Thanks! --->

### Issue Report Checklist

* [X] Searched the [issues page](https://github.com/spyder-ide/spyder/issues?q=is%3Aissue) for similar reports
* [X] Tried basic troubleshooting (if a bug/error)
    * [X] Restarted Spyder
    * [X] Reset preferences with ``spyder --reset``

## Problem Description
I am using Spyder 5.3.0, installed in mac from the distributed .dmg, but need to use an external Python interpreter to debug with packages not present in the "spyder" Python. When the external Python is started, the path has been reset to that of the Python interpreter running Spyder. 


### What steps reproduce the problem?

1. Change interpreter setting for Python interpreter
2. restart console
3. display sys.path

### What is the expected output? What do you see instead?
Python should run normally and packages should load properly. But, this does not happen. 

### Paste Traceback/Error Below (if applicable)
<!--- Copy from error dialog or View > Panes > Internal Console --->
Output in console
```
Python 3.9.12 (main, Apr  5 2022, 01:53:17)
Type "copyright", "credits" or "license" for more information.

IPython 7.31.1 -- An enhanced Interactive Python.

Fatal Python error: init_import_site: Failed to import the site module
Python runtime state: initialized
Traceback (most recent call last):
  File "site.pyc", line 32, in <module>
  File "site.pyc", line 29, in _import_os
  File "os.pyc", line 29, in <module>
  File "_collections_abc.pyc", line 416, in <module>
TypeError: type 'types.GenericAlias' is not an acceptable base type
0.00s - Debugger warning: The os.path.realpath.__code__.co_filename (posixpath.pyc)
0.00s - is not absolute, which may make the debugger miss breakpoints.
0.00s - Note: Debugging will proceed. Set PYDEVD_DISABLE_FILE_VALIDATION=1 to disable this validation.

In[1]: ﻿import sys

In[2]: sys.executable
Out[2]: '/Users/toby/conda39/envs/spyder530/bin/python'

In[3]: sys.path
Out[3]: 
['/Users/toby/Applications/Spyder530.app/Contents/Resources',
 '/Users/toby/Applications/Spyder530.app/Contents/Resources/lib/python39.zip',
 '/Users/toby/Applications/Spyder530.app/Contents/Resources/lib/python3.9',
 '/Users/toby/Applications/Spyder530.app/Contents/Resources/lib/python3.9/lib-dynload',
 '/Users/toby/Applications/Spyder530.app/Contents/Resources/lib/python3.9/site-packages.zip',
 '',
 '/Users/toby/Applications/Spyder530.app/Contents/Resources/lib/python3.9/site-packages',
 '/Users/toby/Applications/Spyder530.app/Contents/Resources/lib/python3.9/IPython/extensions',
 '/Users/toby/Applications/Spyder501.app/Contents/Resources/sn_0_22672798',
 '/Users/toby/.ipython']
```
Note that the Python image I am running is in `~/conda39/envs/spyder530/bin` but the path is referencing locations in `~/Applications/Spyder530.app`, where the latter is where Spyder is installed. 

When `~/conda39/envs/spyder530/bin/python` is run directly:
```
(spyder530) conda39 dev % /Users/toby/conda39/envs/spyder530/bin/python 
Python 3.9.12 (main, Apr  5 2022, 01:53:17) 
[Clang 12.0.0 ] :: Anaconda, Inc. on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/Users/toby/conda39/envs/spyder530/lib/python39.zip', 
'/Users/toby/conda39/envs/spyder530/lib/python3.9', 
'/Users/toby/conda39/envs/spyder530/lib/python3.9/lib-dynload', 
'/Users/toby/conda39/envs/spyder530/lib/python3.9/site-packages']
>>> 
```


## Versions
<!--- You can get this information from Help > About Spyder...
or (if Spyder won't launch) the "conda list" command
from the Anaconda Prompt/Terminal/command line. --->

* Spyder version:  5.3.0
* Python version: 3.9.5
* Qt version:  5.15.2
* PyQt version:  5.15.6
* Operating System name/version: MacOS 10.15.7

### Dependencies
<!--- Please go to the menu entry Help > Dependencies,
press the Copy to clipboard button and paste below --->

```
# Mandatory:
applaunchservices >=0.1.7            :  0.2.1 (OK)
atomicwrites >=1.2.0                 :  1.4.0 (OK)
chardet >=2.0.0                      :  4.0.0 (OK)
cloudpickle >=0.5.0                  :  2.0.0 (OK)
cookiecutter >=1.6.0                 :  1.7.3 (OK)
diff_match_patch >=20181111          :  20200713 (OK)
intervaltree >=3.0.2                 :  3.1.0 (OK)
IPython >=7.31.1;<8.0.0              :  7.32.0 (OK)
jedi >=0.17.2;<0.19.0                :  0.18.1 (OK)
jellyfish >=0.7                      :  0.9.0 (OK)
jsonschema >=3.2.0                   :  4.4.0 (OK)
keyring >=17.0.0                     :  23.5.0 (OK)
nbconvert >=4.0                      :  6.4.5 (OK)
numpydoc >=0.6.0                     :  1.2.1 (OK)
parso >=0.7.0;<0.9.0                 :  0.8.3 (OK)
pexpect >=4.4.0                      :  4.8.0 (OK)
pickleshare >=0.4                    :  0.7.5 (OK)
psutil >=5.3                         :  5.9.0 (OK)
pygments >=2.0                       :  2.11.2 (OK)
pylint >=2.5.0                       :  2.13.3 (OK)
pyls_spyder >=0.4.0                  :  0.4.0 (OK)
pylsp >=1.4.1;<1.5.0                 :  1.4.1 (OK)
pylsp_black >=1.2.0                  :  1.2.0 (OK)
qdarkstyle >=3.0.2;<3.1.0            :  3.0.3 (OK)
qstylizer >=0.1.10                   :  0.2.1 (OK)
qtawesome >=1.0.2                    :  1.1.1 (OK)
qtconsole >=5.3.0;<5.4.0             :  5.3.0 (OK)
qtpy >=2.0.1                         :  2.0.1 (OK)
rtree >=0.9.7                        :  0.9.7 (OK)
setuptools >=49.6.0                  :  60.10.0 (OK)
sphinx >=0.6.6                       :  4.5.0 (OK)
spyder_kernels >=2.3.0;<2.4.0        :  2.3.0 (OK)
textdistance >=4.2.0                 :  4.2.2 (OK)
three_merge >=0.1.1                  :  0.1.1 (OK)
watchdog >=0.10.3                    :  2.1.7 (OK)
zmq >=17                             :  22.3.0 (OK)

# Optional:
cython >=0.21                        :  0.29.28 (OK)
matplotlib >=3.0.0                   :  3.5.1 (OK)
numpy >=1.7                          :  1.22.3 (OK)
pandas >=1.1.1                       :  1.4.1 (OK)
scipy >=0.17.0                       :  1.8.0 (OK)
sympy >=0.7.3                        :  1.10.1 (OK)

# Spyder plugins:
spyder_terminal.terminalplugin 1.2.2 :  1.2.2 (OK)
```



Discussion:
Hi @briantoby thank you for the feedback! I'm not totally sure what could be happening here :/ Is this hapenning with any environment interpreter you try to use? Just in case, pinging @mrclary for more ideas 
@briantoby, this is strange behavior, indeed. However, I think the issue is that you have renamed the application from 'Spyder.app' to 'Spyder530.app'. Also, `'/Users/toby/Applications/Spyder501.app/Contents/Resources/sn_0_22672798'` suggests that you have another 'Spyder501.app' as well.

IPython consoles will not open properly if the Spyder application has been renamed. There are several things in the Spyder source code that check explicitly for 'Spyder.app', so if you rename the Spyder application bundle the application will not run properly. Will you please try renaming the application bundle 'Spyder.app'? I think this should resolve your issue.

@dalthviz and @ccordoba12, I'll investigate updating our use of `MAC_APP_NAME` to see if Spyder can be made robust against users renaming the application bundle.
Thanks @mrclary !

Resolving patch:
diff --git a/installers/macOS/setup.py b/installers/macOS/setup.py
--- a/installers/macOS/setup.py
+++ b/installers/macOS/setup.py
@@ -18,7 +18,6 @@
 from setuptools import setup
 
 from spyder import __version__ as SPYVER
-from spyder.config.base import MAC_APP_NAME
 
 # Setup logger
 fmt = Formatter('%(asctime)s [%(levelname)s] [%(name)s] -> %(message)s')
@@ -34,6 +33,7 @@
 ICONFILE = SPYREPO / 'img_src' / 'spyder.icns'
 APPSCRIPT = SPYREPO / 'scripts' / 'spyder'
 
+MAC_APP_NAME = 'Spyder.app'
 APP_BASE_NAME = MAC_APP_NAME[:-4]
 
 # Python version
@@ -118,7 +118,6 @@ def make_disk_image(dist_dir, make_lite=False):
     dmg_name += '.dmg'
     dmgfile = (dist_dir / dmg_name).as_posix()
 
-
     settings_file = (THISDIR / 'dmg_settings.py').as_posix()
     settings = {
         'files': [(dist_dir / MAC_APP_NAME).as_posix()],
diff --git a/spyder/config/base.py b/spyder/config/base.py
--- a/spyder/config/base.py
+++ b/spyder/config/base.py
@@ -286,7 +286,7 @@ def get_conf_paths():
         search_paths = []
         tmpfolder = str(tempfile.gettempdir())
         for i in range(3):
-            path = os.path.join(tmpfolder, 'site-config-'+str(i))
+            path = os.path.join(tmpfolder, 'site-config-' + str(i))
             if not os.path.isdir(path):
                 os.makedirs(path)
             search_paths.append(path)
@@ -356,6 +356,7 @@ def is_pynsist():
         return pkgs_path in os.environ.get('PYTHONPATH')
     return False
 
+
 #==============================================================================
 # Translations
 #==============================================================================
@@ -380,6 +381,7 @@ def is_pynsist():
 # Disabled languages because their translations are outdated or incomplete
 DISABLED_LANGUAGES = ['hu', 'pl']
 
+
 def get_available_translations():
     """
     List available translations for spyder based on the folders found in the
@@ -394,7 +396,7 @@ def get_available_translations():
     langs = [DEFAULT_LANGUAGE] + langs
 
     # Remove disabled languages
-    langs = list( set(langs) - set(DISABLED_LANGUAGES) )
+    langs = list(set(langs) - set(DISABLED_LANGUAGES))
 
     # Check that there is a language code available in case a new translation
     # is added, to ensure LANGUAGE_CODES is updated.
@@ -513,6 +515,7 @@ def translate_dumb(x):
     try:
         _trans = gettext.translation(modname, locale_path, codeset="utf-8")
         lgettext = _trans.lgettext
+
         def translate_gettext(x):
             if not PY3 and is_unicode(x):
                 x = x.encode("utf-8")
@@ -525,6 +528,7 @@ def translate_gettext(x):
     except Exception:
         return translate_dumb
 
+
 # Translation callback
 _ = get_translation("spyder")
 
@@ -534,23 +538,17 @@ def translate_gettext(x):
 #==============================================================================
 # Variable explorer display / check all elements data types for sequences:
 # (when saving the variable explorer contents, check_all is True,
-CHECK_ALL = False #XXX: If True, this should take too much to compute...
+CHECK_ALL = False  # XXX: If True, this should take too much to compute...
 
 EXCLUDED_NAMES = ['nan', 'inf', 'infty', 'little_endian', 'colorbar_doc',
                   'typecodes', '__builtins__', '__main__', '__doc__', 'NaN',
                   'Inf', 'Infinity', 'sctypes', 'rcParams', 'rcParamsDefault',
-                  'sctypeNA', 'typeNA', 'False_', 'True_',]
+                  'sctypeNA', 'typeNA', 'False_', 'True_']
 
 
 #==============================================================================
 # Mac application utilities
 #==============================================================================
-if PY3:
-    MAC_APP_NAME = 'Spyder.app'
-else:
-    MAC_APP_NAME = 'Spyder-Py2.app'
-
-
 def running_in_mac_app(pyexec=None):
     """
     Check if Python executable is located inside a standalone Mac app.
@@ -564,14 +562,29 @@ def running_in_mac_app(pyexec=None):
     if pyexec is None:
         pyexec = sys.executable
 
-    if sys.platform == "darwin":
-        if MAC_APP_NAME not in pyexec:
-            return False
+    bpath = get_mac_app_bundle_path()
+
+    if bpath and pyexec == osp.join(bpath, 'Contents/MacOS/python'):
         return True
     else:
         return False
 
 
+def get_mac_app_bundle_path():
+    """
+    Return the full path to the macOS app bundle. Otherwise return None.
+
+    EXECUTABLEPATH environment variable only exists if Spyder is a macOS app
+    bundle. In which case it will always end with
+    "/<app name>.app/Conents/MacOS/Spyder".
+    """
+    app_exe_path = os.environ.get('EXECUTABLEPATH', None)
+    if sys.platform == "darwin" and app_exe_path:
+        return osp.dirname(osp.dirname(osp.dirname(osp.abspath(app_exe_path))))
+    else:
+        return None
+
+
 # =============================================================================
 # Micromamba
 # =============================================================================
diff --git a/spyder/utils/qthelpers.py b/spyder/utils/qthelpers.py
--- a/spyder/utils/qthelpers.py
+++ b/spyder/utils/qthelpers.py
@@ -20,15 +20,14 @@
 from qtpy.compat import from_qvariant, to_qvariant
 from qtpy.QtCore import (QEvent, QLibraryInfo, QLocale, QObject, Qt, QTimer,
                          QTranslator, QUrl, Signal, Slot)
-from qtpy.QtGui import (
-    QDesktopServices, QIcon, QKeyEvent, QKeySequence, QPixmap)
+from qtpy.QtGui import QDesktopServices, QKeyEvent, QKeySequence, QPixmap
 from qtpy.QtWidgets import (QAction, QApplication, QDialog, QHBoxLayout,
                             QLabel, QLineEdit, QMenu, QPlainTextEdit,
-                            QProxyStyle, QPushButton, QStyle, QToolBar,
+                            QProxyStyle, QPushButton, QStyle,
                             QToolButton, QVBoxLayout, QWidget)
 
 # Local imports
-from spyder.config.base import MAC_APP_NAME
+from spyder.config.base import get_mac_app_bundle_path
 from spyder.config.manager import CONF
 from spyder.py3compat import configparser, is_text_string, to_text_string, PY2
 from spyder.utils.icon_manager import ima
@@ -37,7 +36,6 @@
 from spyder.utils.palette import QStylePalette
 from spyder.utils.registries import ACTION_REGISTRY, TOOLBUTTON_REGISTRY
 from spyder.widgets.waitingspinner import QWaitingSpinner
-from spyder.config.manager import CONF
 
 # Third party imports
 if sys.platform == "darwin":
@@ -129,7 +127,7 @@ def qapplication(translate=True, test_time=3):
     if test_ci is not None:
         timer_shutdown = QTimer(app)
         timer_shutdown.timeout.connect(app.quit)
-        timer_shutdown.start(test_time*1000)
+        timer_shutdown.start(test_time * 1000)
     return app
 
 
@@ -147,14 +145,17 @@ def file_uri(fname):
 
 
 QT_TRANSLATOR = None
+
+
 def install_translator(qapp):
     """Install Qt translator to the QApplication instance"""
     global QT_TRANSLATOR
     if QT_TRANSLATOR is None:
         qt_translator = QTranslator()
-        if qt_translator.load("qt_"+QLocale.system().name(),
-                      QLibraryInfo.location(QLibraryInfo.TranslationsPath)):
-            QT_TRANSLATOR = qt_translator # Keep reference alive
+        if qt_translator.load(
+                "qt_" + QLocale.system().name(),
+                QLibraryInfo.location(QLibraryInfo.TranslationsPath)):
+            QT_TRANSLATOR = qt_translator  # Keep reference alive
     if QT_TRANSLATOR is not None:
         qapp.installTranslator(QT_TRANSLATOR)
 
@@ -382,6 +383,7 @@ def wrapped_toggled(value):
 
 def add_configuration_update(action):
     """Add on_configuration_change to a SpyderAction that depends on CONF."""
+
     def on_configuration_change(self, _option, _section, value):
         self.blockSignals(True)
         self.setChecked(value)
@@ -512,6 +514,7 @@ class DialogManager(QObject):
     Object that keep references to non-modal dialog boxes for another QObject,
     typically a QMainWindow or any kind of QWidget
     """
+
     def __init__(self):
         QObject.__init__(self)
         self.dialogs = {}
@@ -529,9 +532,9 @@ def show(self, dialog):
             dialog.show()
             self.dialogs[id(dialog)] = dialog
             dialog.accepted.connect(
-                              lambda eid=id(dialog): self.dialog_finished(eid))
+                lambda eid=id(dialog): self.dialog_finished(eid))
             dialog.rejected.connect(
-                              lambda eid=id(dialog): self.dialog_finished(eid))
+                lambda eid=id(dialog): self.dialog_finished(eid))
 
     def dialog_finished(self, dialog_id):
         """Manage non-modal dialog boxes"""
@@ -548,7 +551,7 @@ def get_filetype_icon(fname):
     ext = osp.splitext(fname)[1]
     if ext.startswith('.'):
         ext = ext[1:]
-    return ima.get_icon( "%s.png" % ext, ima.icon('FileIcon') )
+    return ima.get_icon("%s.png" % ext, ima.icon('FileIcon'))
 
 
 class SpyderAction(QAction):
@@ -572,6 +575,7 @@ class ShowStdIcons(QWidget):
     """
     Dialog showing standard icons
     """
+
     def __init__(self, parent):
         QWidget.__init__(self, parent)
         layout = QHBoxLayout()
@@ -585,10 +589,10 @@ def __init__(self, parent):
                 icon = ima.get_std_icon(child)
                 label = QLabel()
                 label.setPixmap(icon.pixmap(32, 32))
-                icon_layout.addWidget( label )
-                icon_layout.addWidget( QLineEdit(child.replace('SP_', '')) )
+                icon_layout.addWidget(label)
+                icon_layout.addWidget(QLineEdit(child.replace('SP_', '')))
                 col_layout.addLayout(icon_layout)
-                cindex = (cindex+1) % row_nb
+                cindex = (cindex + 1) % row_nb
                 if cindex == 0:
                     layout.addLayout(col_layout)
         self.setLayout(layout)
@@ -751,7 +755,7 @@ def event(self, event):
                 pass
             elif self._has_started:
                 self.sig_open_external_file.emit(fname)
-            elif MAC_APP_NAME not in fname:
+            else:
                 self._pending_file_open.append(fname)
         return QApplication.event(self, event)
 
@@ -771,16 +775,14 @@ def register_app_launchservices(
     Register app to the Apple launch services so it can open Python files
     """
     app = QApplication.instance()
-    # If top frame is MAC_APP_NAME, set ourselves to open files at startup
-    origin_filename = get_origin_filename()
-    if MAC_APP_NAME in origin_filename:
-        bundle_idx = origin_filename.find(MAC_APP_NAME)
-        old_handler = als.get_bundle_identifier_for_path(
-            origin_filename[:bundle_idx] + MAC_APP_NAME)
+
+    # If macOS app, set ourselves to open files at startup
+    bundle = get_mac_app_bundle_path()
+    if bundle:
+        old_handler = als.get_bundle_identifier_for_path(bundle)
     else:
         # Else, just restore the previous handler
-        old_handler = als.get_UTI_handler(
-            uniform_type_identifier, role)
+        old_handler = als.get_UTI_handler(uniform_type_identifier, role)
 
     app._original_handlers[(uniform_type_identifier, role)] = old_handler
 


Test patch: