summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--cmd2/cmd2.py55
-rw-r--r--docs/features/embedded_python_shells.rst24
3 files changed, 42 insertions, 38 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4f3b9472..cde4f34d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -37,6 +37,7 @@
attribute added to the cmd2 instance itself.
* Raising ``SystemExit`` or calling ``sys.exit()`` in a command or hook function will set ``self.exit_code``
to the exit code used in those calls. It will also result in the command loop stopping.
+ * ipy command now includes all of `self.py_locals` in the IPython environment
## 1.5.0 (January 31, 2021)
* Bug Fixes
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 68ad8201..b6c44eb3 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -4059,13 +4059,13 @@ class Cmd(cmd.Cmd):
# This is to prevent pyscripts from editing it. (e.g. locals().clear()). It also ensures a pyscript's
# environment won't be filled with data from a previously run pyscript. Only make a shallow copy since
# it's OK for py_locals to contain objects which are editable in a pyscript.
- localvars = dict(self.py_locals)
- localvars[self.py_bridge_name] = py_bridge
- localvars['quit'] = py_quit
- localvars['exit'] = py_quit
+ local_vars = self.py_locals.copy()
+ local_vars[self.py_bridge_name] = py_bridge
+ local_vars['quit'] = py_quit
+ local_vars['exit'] = py_quit
if self.self_in_py:
- localvars['self'] = self
+ local_vars['self'] = self
# Handle case where we were called by run_pyscript
if pyscript is not None:
@@ -4079,8 +4079,8 @@ class Cmd(cmd.Cmd):
self.pexcept("Error reading script file '{}': {}".format(expanded_filename, ex))
return
- localvars['__name__'] = '__main__'
- localvars['__file__'] = expanded_filename
+ local_vars['__name__'] = '__main__'
+ local_vars['__file__'] = expanded_filename
# Place the script's directory at sys.path[0] just as Python does when executing a script
saved_sys_path = list(sys.path)
@@ -4088,7 +4088,7 @@ class Cmd(cmd.Cmd):
else:
# This is the default name chosen by InteractiveConsole when no locals are passed in
- localvars['__name__'] = '__console__'
+ local_vars['__name__'] = '__console__'
if args.command:
py_code_to_run = args.command
@@ -4100,7 +4100,7 @@ class Cmd(cmd.Cmd):
py_bridge.cmd_echo = True
# Create the Python interpreter
- interp = InteractiveConsole(locals=localvars)
+ interp = InteractiveConsole(locals=local_vars)
# Check if we are running Python code
if py_code_to_run:
@@ -4201,24 +4201,20 @@ class Cmd(cmd.Cmd):
PyBridge,
)
- # noinspection PyUnusedLocal
- def load_ipy(cmd2_app: Cmd, py_bridge: PyBridge):
+ def load_ipy(ipy_locals: Dict[str, Any]) -> None:
"""
Embed an IPython shell in an environment that is restricted to only the variables in this function
- :param cmd2_app: instance of the cmd2 app
- :param py_bridge: a PyBridge
+ :param ipy_locals: locals dictionary for the IPython environment
"""
- # Create a variable pointing to py_bridge and name it using the value of py_bridge_name
- exec("{} = py_bridge".format(cmd2_app.py_bridge_name))
+ # Copy ipy_locals into this function's locals
+ for key, val in ipy_locals.items():
+ locals()[key] = val
- # Add self variable pointing to cmd2_app, if allowed
- if cmd2_app.self_in_py:
- exec("self = cmd2_app")
-
- # Delete these names from the environment so IPython can't use them
- del cmd2_app
- del py_bridge
+ # Delete these names from the environment so IPython won't see them
+ del key
+ del val
+ del ipy_locals
# Start ipy shell
embed(
@@ -4235,9 +4231,18 @@ class Cmd(cmd.Cmd):
try:
self._in_py = True
- new_py_bridge = PyBridge(self)
- load_ipy(self, new_py_bridge)
- return new_py_bridge.stop
+ py_bridge = PyBridge(self)
+
+ # Make a copy of self.py_locals for the locals dictionary in the IPython environment we are creating.
+ # This is to prevent ipy from editing it. (e.g. locals().clear()). Only make a shallow copy since
+ # it's OK for py_locals to contain objects which are editable in ipy.
+ local_vars = self.py_locals.copy()
+ local_vars[self.py_bridge_name] = py_bridge
+ if self.self_in_py:
+ local_vars['self'] = self
+
+ load_ipy(local_vars)
+ return py_bridge.stop
finally:
self._in_py = False
diff --git a/docs/features/embedded_python_shells.rst b/docs/features/embedded_python_shells.rst
index 8ff65ffe..71fb8197 100644
--- a/docs/features/embedded_python_shells.rst
+++ b/docs/features/embedded_python_shells.rst
@@ -8,19 +8,16 @@ arguments, it enters an interactive Python session. The session can call
your ``cmd2`` application while maintaining isolation.
You may optionally enable full access to to your application by setting
-``self_in_py`` to ``True``. Enabling this flag adds ``self`` to the python
-session, which is a reference to your ``cmd2`` application. This can be useful
-for debugging your application.
+``self.self_in_py`` to ``True``. Enabling this flag adds ``self`` to the
+python session, which is a reference to your ``cmd2`` application. This can be
+useful for debugging your application.
+
+Anything in ``self.py_locals`` is always available in the Python environment.
The ``app`` object (or your custom name) provides access to application
commands through raw commands. For example, any application command call be
called with ``app("<command>")``.
-::
-
- >>> app('say --piglatin Blah')
- lahBay
-
More Python examples:
::
@@ -114,12 +111,13 @@ be present::
The ``ipy`` command enters an interactive IPython_ session. Similar to an
interactive Python session, this shell can access your application instance via
-``self`` and any changes to your application made via ``self`` will persist.
-However, any local or global variable created within the ``ipy`` shell will not
-persist. Within the ``ipy`` shell, you cannot call "back" to your application
-with ``cmd("")``, however you can run commands directly like so::
+``self`` if ``self.self_in_py`` is ``True`` and any changes to your application
+made via ``self`` will persist. However, any local or global variable created
+within the ``ipy`` shell will not persist.
- self.onecmd_plus_hooks('help')
+Also, as in the interactive Python session, the ``ipy`` shell has access to the
+contents of ``self.py_locals`` and can call back into the application using the
+``app`` object (or your custom name).
IPython_ provides many advantages, including: