Skip to content

Commit ae03c59

Browse files
committed
ipy command now includes all of self.py_locals in the IPython environment
1 parent f1aea75 commit ae03c59

File tree

3 files changed

+42
-38
lines changed

3 files changed

+42
-38
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
attribute added to the cmd2 instance itself.
3838
* Raising ``SystemExit`` or calling ``sys.exit()`` in a command or hook function will set ``self.exit_code``
3939
to the exit code used in those calls. It will also result in the command loop stopping.
40+
* ipy command now includes all of `self.py_locals` in the IPython environment
4041

4142
## 1.5.0 (January 31, 2021)
4243
* Bug Fixes

cmd2/cmd2.py

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4059,13 +4059,13 @@ def py_quit():
40594059
# This is to prevent pyscripts from editing it. (e.g. locals().clear()). It also ensures a pyscript's
40604060
# environment won't be filled with data from a previously run pyscript. Only make a shallow copy since
40614061
# it's OK for py_locals to contain objects which are editable in a pyscript.
4062-
localvars = dict(self.py_locals)
4063-
localvars[self.py_bridge_name] = py_bridge
4064-
localvars['quit'] = py_quit
4065-
localvars['exit'] = py_quit
4062+
local_vars = self.py_locals.copy()
4063+
local_vars[self.py_bridge_name] = py_bridge
4064+
local_vars['quit'] = py_quit
4065+
local_vars['exit'] = py_quit
40664066

40674067
if self.self_in_py:
4068-
localvars['self'] = self
4068+
local_vars['self'] = self
40694069

40704070
# Handle case where we were called by run_pyscript
40714071
if pyscript is not None:
@@ -4079,16 +4079,16 @@ def py_quit():
40794079
self.pexcept("Error reading script file '{}': {}".format(expanded_filename, ex))
40804080
return
40814081

4082-
localvars['__name__'] = '__main__'
4083-
localvars['__file__'] = expanded_filename
4082+
local_vars['__name__'] = '__main__'
4083+
local_vars['__file__'] = expanded_filename
40844084

40854085
# Place the script's directory at sys.path[0] just as Python does when executing a script
40864086
saved_sys_path = list(sys.path)
40874087
sys.path.insert(0, os.path.dirname(os.path.abspath(expanded_filename)))
40884088

40894089
else:
40904090
# This is the default name chosen by InteractiveConsole when no locals are passed in
4091-
localvars['__name__'] = '__console__'
4091+
local_vars['__name__'] = '__console__'
40924092

40934093
if args.command:
40944094
py_code_to_run = args.command
@@ -4100,7 +4100,7 @@ def py_quit():
41004100
py_bridge.cmd_echo = True
41014101

41024102
# Create the Python interpreter
4103-
interp = InteractiveConsole(locals=localvars)
4103+
interp = InteractiveConsole(locals=local_vars)
41044104

41054105
# Check if we are running Python code
41064106
if py_code_to_run:
@@ -4201,24 +4201,20 @@ def do_ipy(self, _: argparse.Namespace) -> Optional[bool]:
42014201
PyBridge,
42024202
)
42034203

4204-
# noinspection PyUnusedLocal
4205-
def load_ipy(cmd2_app: Cmd, py_bridge: PyBridge):
4204+
def load_ipy(ipy_locals: Dict[str, Any]) -> None:
42064205
"""
42074206
Embed an IPython shell in an environment that is restricted to only the variables in this function
42084207
4209-
:param cmd2_app: instance of the cmd2 app
4210-
:param py_bridge: a PyBridge
4208+
:param ipy_locals: locals dictionary for the IPython environment
42114209
"""
4212-
# Create a variable pointing to py_bridge and name it using the value of py_bridge_name
4213-
exec("{} = py_bridge".format(cmd2_app.py_bridge_name))
4210+
# Copy ipy_locals into this function's locals
4211+
for key, val in ipy_locals.items():
4212+
locals()[key] = val
42144213

4215-
# Add self variable pointing to cmd2_app, if allowed
4216-
if cmd2_app.self_in_py:
4217-
exec("self = cmd2_app")
4218-
4219-
# Delete these names from the environment so IPython can't use them
4220-
del cmd2_app
4221-
del py_bridge
4214+
# Delete these names from the environment so IPython won't see them
4215+
del key
4216+
del val
4217+
del ipy_locals
42224218

42234219
# Start ipy shell
42244220
embed(
@@ -4235,9 +4231,18 @@ def load_ipy(cmd2_app: Cmd, py_bridge: PyBridge):
42354231

42364232
try:
42374233
self._in_py = True
4238-
new_py_bridge = PyBridge(self)
4239-
load_ipy(self, new_py_bridge)
4240-
return new_py_bridge.stop
4234+
py_bridge = PyBridge(self)
4235+
4236+
# Make a copy of self.py_locals for the locals dictionary in the IPython environment we are creating.
4237+
# This is to prevent ipy from editing it. (e.g. locals().clear()). Only make a shallow copy since
4238+
# it's OK for py_locals to contain objects which are editable in ipy.
4239+
local_vars = self.py_locals.copy()
4240+
local_vars[self.py_bridge_name] = py_bridge
4241+
if self.self_in_py:
4242+
local_vars['self'] = self
4243+
4244+
load_ipy(local_vars)
4245+
return py_bridge.stop
42414246
finally:
42424247
self._in_py = False
42434248

docs/features/embedded_python_shells.rst

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,16 @@ arguments, it enters an interactive Python session. The session can call
88
your ``cmd2`` application while maintaining isolation.
99

1010
You may optionally enable full access to to your application by setting
11-
``self_in_py`` to ``True``. Enabling this flag adds ``self`` to the python
12-
session, which is a reference to your ``cmd2`` application. This can be useful
13-
for debugging your application.
11+
``self.self_in_py`` to ``True``. Enabling this flag adds ``self`` to the
12+
python session, which is a reference to your ``cmd2`` application. This can be
13+
useful for debugging your application.
14+
15+
Anything in ``self.py_locals`` is always available in the Python environment.
1416

1517
The ``app`` object (or your custom name) provides access to application
1618
commands through raw commands. For example, any application command call be
1719
called with ``app("<command>")``.
1820

19-
::
20-
21-
>>> app('say --piglatin Blah')
22-
lahBay
23-
2421
More Python examples:
2522

2623
::
@@ -114,12 +111,13 @@ be present::
114111

115112
The ``ipy`` command enters an interactive IPython_ session. Similar to an
116113
interactive Python session, this shell can access your application instance via
117-
``self`` and any changes to your application made via ``self`` will persist.
118-
However, any local or global variable created within the ``ipy`` shell will not
119-
persist. Within the ``ipy`` shell, you cannot call "back" to your application
120-
with ``cmd("")``, however you can run commands directly like so::
114+
``self`` if ``self.self_in_py`` is ``True`` and any changes to your application
115+
made via ``self`` will persist. However, any local or global variable created
116+
within the ``ipy`` shell will not persist.
121117

122-
self.onecmd_plus_hooks('help')
118+
Also, as in the interactive Python session, the ``ipy`` shell has access to the
119+
contents of ``self.py_locals`` and can call back into the application using the
120+
``app`` object (or your custom name).
123121

124122
IPython_ provides many advantages, including:
125123

0 commit comments

Comments
 (0)