diff --git a/HISTORY.txt b/HISTORY.txt index 62b0a1f..b0a35be 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -4,6 +4,10 @@ Changelog 0.13.14 (unreleased) -------------------- +- Add ipython_profile support. We can now run ipdb with a non-default + ipython-profile by setting an environment-variable ``IPDB_IPYTHON_PROFILE`` + or by setting ``ipython_profile`` in the config. [WouterVH] + - Run ``black`` on ipdb-codebase with line-length 88. [WouterVH] diff --git a/README.rst b/README.rst index 96f6882..8a5fff0 100644 --- a/README.rst +++ b/README.rst @@ -120,6 +120,27 @@ Or you can use ``iex`` as a function decorator to launch ipdb if an exception is Using ``from future import print_function`` for Python 3 compat implies dropping Python 2.5 support. Use ``ipdb<=0.8`` with 2.5. + +Using a non-default ipython-profile +----------------------------------- +By default ``ipdb`` will instantiate an ipython-session loaded with the default profile called ``default``. +You can set a non-default profile by setting the environment variable ``IPDB_IPYTON_PROFILE``: + +.. code-block:: bash + + export IPDB_IPYTON_PROFILE="ipdb" + +Or by setting in ``pyproject.toml``: + +.. code-block:: toml + + [tool.ipdb] + ipython_profile = "ipdb" + +This should correspond with a profile-directory ``profile_ipdb```in your ``IPYTHON_HOME``. +If this profile-directory does not exist, we fall back to the default profile. + + Issues with ``stdout`` ---------------------- diff --git a/ipdb/__main__.py b/ipdb/__main__.py index c4b649a..56376f9 100644 --- a/ipdb/__main__.py +++ b/ipdb/__main__.py @@ -13,7 +13,10 @@ __version__ = "0.13.14.dev0" from IPython import get_ipython +from IPython.core.application import ProfileDir from IPython.core.debugger import BdbQuit_excepthook +from IPython.core.profiledir import ProfileDirError +from IPython.paths import get_ipython_dir from IPython.terminal.ipapp import TerminalIPythonApp from IPython.terminal.embed import InteractiveShellEmbed @@ -23,20 +26,30 @@ import ConfigParser as configparser -def _get_debugger_cls(): +def _get_debugger_cls(ipython_profile="default"): shell = get_ipython() if shell is None: # Not inside IPython # Build a terminal app in order to force ipython to load the # configuration - ipapp = TerminalIPythonApp() + ipython_dir = get_ipython_dir() + try: + profile_dir = ProfileDir.find_profile_dir_by_name( + ipython_dir=ipython_dir, + name=ipython_profile, + ) + except ProfileDirError: # fallback to default-profile + profile_dir = ProfileDir.find_profile_dir_by_name( + ipython_dir=ipython_dir, + ) + ipapp = TerminalIPythonApp(profile_dir=profile_dir) + # Avoid output (banner, prints) ipapp.interact = False ipapp.initialize(["--no-term-title"]) shell = ipapp.shell else: # Running inside IPython - # Detect if embed shell or not and display a message if isinstance(shell, InteractiveShellEmbed): sys.stderr.write( @@ -49,10 +62,17 @@ def _get_debugger_cls(): return shell.debugger_cls -def _init_pdb(context=None, commands=[]): +def _init_pdb(context=None, ipython_profile=None, commands=[]): if context is None: context = os.getenv("IPDB_CONTEXT_SIZE", get_context_from_config()) - debugger_cls = _get_debugger_cls() + + if ipython_profile is None: + ipython_profile = os.getenv( + "IPDB_IPYTHON_PROFILE", get_ipython_profile_from_config() + ) + + debugger_cls = _get_debugger_cls(ipython_profile=ipython_profile) + try: p = debugger_cls(context=context) except TypeError: @@ -94,6 +114,14 @@ def get_context_from_config(): ) +def get_ipython_profile_from_config(): + parser = get_config() + try: + return parser.get("ipdb", "ipython_profile") + except (configparser.NoSectionError, configparser.NoOptionError): + return "default" + + class ConfigFile(object): """ Filehandle wrapper that adds a "[ipdb]" section to the start of a config diff --git a/tests/test_config.py b/tests/test_config.py index e4fc7e7..4ae06b6 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -16,6 +16,7 @@ from ipdb.__main__ import ( get_config, get_context_from_config, + get_ipython_profile_from_config, ) @@ -137,6 +138,22 @@ def test_noenv_nodef_nosetup_pyproject(self): self.assertEqual(self.pyproject_context, cfg.getint("ipdb", "context")) self.assertRaises(configparser.NoOptionError, cfg.get, "ipdb", "version") + def test_noenv_nodef_nosetup_pyproject(self): + """ + Setup: $IPDB_CONFIG unset, $HOME/.ipdb does not exist, + setup.cfg does not exist, pyproject.toml exists + Result: load pyproject.toml + """ + os.unlink(self.env_filename) + os.unlink(self.default_filename) + os.remove(self.setup_filename) + with ModifiedEnvironment(IPDB_CONFIG=None, HOME=self.tmpd): + cfg = get_config() + # breakpoint() + self.assertEqual(["ipdb"], cfg.sections()) + self.assertEqual(self.pyproject_context, cfg.getint("ipdb", "context")) + self.assertRaises(configparser.NoOptionError, cfg.get, "ipdb", "version") + def test_env_nodef_setup_pyproject(self): """ Setup: $IPDB_CONFIG is set, $HOME/.ipdb does not exist, @@ -382,3 +399,72 @@ def test_noenv_nodef_invalid_setup(self): pass else: self.fail("Expected TomlDecodeError from invalid config file") + + +class get_ipython_profile_from_config_TestCase(unittest.TestCase): + """ + Test cases for function `get_ipython_profile_from_config`. + """ + + def setUp(self): + """ + Set fixtures for this test case. + """ + set_config_files_fixture(self) + + def test_missing_profile_setup(self): + """ + Setup: pyproject.toml has no [tool.ipdb]-section. + """ + os.unlink(self.env_filename) + os.unlink(self.default_filename) + os.unlink(self.setup_filename) + write_lines_to_file( + self.pyproject_filename, + "" + ) + + with ModifiedEnvironment(IPDB_CONFIG=None, HOME=self.tmpd): + profile_name = get_ipython_profile_from_config() + assert profile_name == "default" + + def test_default_profile_setup(self): + """ + Setup: pyproject.toml has a [tool.ipdb]-section + with the ipython_profile explicitly set to 'default'. + + """ + os.unlink(self.env_filename) + os.unlink(self.default_filename) + os.unlink(self.setup_filename) + write_lines_to_file( + self.pyproject_filename, + [ + "[tool.ipdb]", + "ipython_profile = 'default'", + ], + ) + + with ModifiedEnvironment(IPDB_CONFIG=None, HOME=self.tmpd): + profile_name = get_ipython_profile_from_config() + assert profile_name == "default" + + def test_non_default_profile_setup(self): + """ + Setup: pyproject.toml has a [tool.ipdb]-section + with the ipython_profile explicitly set to a non-default. + """ + os.unlink(self.env_filename) + os.unlink(self.default_filename) + os.unlink(self.setup_filename) + write_lines_to_file( + self.pyproject_filename, + [ + "[tool.ipdb]", + "ipython_profile = 'foo'", + ], + ) + + with ModifiedEnvironment(IPDB_CONFIG=None, HOME=self.tmpd): + profile_name = get_ipython_profile_from_config() + assert profile_name == "foo"