Skip to content

Commit e4e9a34

Browse files
committed
fixes #54
1 parent e380aa2 commit e4e9a34

File tree

3 files changed

+166
-16
lines changed

3 files changed

+166
-16
lines changed

execnb/_modidx.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
'execnb.shell.CaptureShell.set_path': ('shell.html#captureshell.set_path', 'execnb/shell.py'),
3232
'execnb.shell.ExecutionInfo.__repr__': ('shell.html#executioninfo.__repr__', 'execnb/shell.py'),
3333
'execnb.shell.ExecutionResult.__repr__': ('shell.html#executionresult.__repr__', 'execnb/shell.py'),
34+
'execnb.shell.SmartCompleter': ('shell.html#smartcompleter', 'execnb/shell.py'),
35+
'execnb.shell.SmartCompleter.__call__': ('shell.html#smartcompleter.__call__', 'execnb/shell.py'),
36+
'execnb.shell.SmartCompleter.__init__': ('shell.html#smartcompleter.__init__', 'execnb/shell.py'),
3437
'execnb.shell._CustDisplayHook': ('shell.html#_custdisplayhook', 'execnb/shell.py'),
3538
'execnb.shell._CustDisplayHook.log_output': ('shell.html#_custdisplayhook.log_output', 'execnb/shell.py'),
3639
'execnb.shell._CustDisplayHook.write_format_data': ( 'shell.html#_custdisplayhook.write_format_data',

execnb/shell.py

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@
88
from fastcore.utils import *
99
from fastcore.script import call_parse
1010

11-
import multiprocessing
11+
import multiprocessing,types
1212
try:
1313
if sys.platform == 'darwin': multiprocessing.set_start_method("fork")
1414
except RuntimeError: pass # if re-running cell
1515

1616
from IPython.core.interactiveshell import InteractiveShell, ExecutionInfo, ExecutionResult
1717
from IPython.core.displayhook import DisplayHook
1818
from IPython.utils.capture import capture_output
19+
from IPython.core.completer import IPCompleter,provisionalcompleter
20+
from IPython.core.hooks import CommandChainDispatcher
21+
from IPython.core.completerlib import module_completer
22+
from IPython.utils.strdispatch import StrDispatch
1923
from IPython.display import display as disp, HTML
2024

2125
import traceback
@@ -27,9 +31,9 @@
2731
from .nbio import _dict2obj
2832

2933
# %% auto 0
30-
__all__ = ['CaptureShell', 'find_output', 'out_exec', 'out_stream', 'out_error', 'exec_nb']
34+
__all__ = ['CaptureShell', 'find_output', 'out_exec', 'out_stream', 'out_error', 'exec_nb', 'SmartCompleter']
3135

32-
# %% ../nbs/02_shell.ipynb 5
36+
# %% ../nbs/02_shell.ipynb 7
3337
class _CustDisplayHook(DisplayHook):
3438
def write_output_prompt(self): pass
3539
def write_format_data(self, data, md_dict): pass
@@ -41,7 +45,7 @@ def __repr__(self: ExecutionInfo): return f'cell: {self.raw_cell}; id: {self.cel
4145
@patch
4246
def __repr__(self: ExecutionResult): return f'result: {self.result}; err: {self.error_in_exec}; info: <{self.info}>'
4347

44-
# %% ../nbs/02_shell.ipynb 6
48+
# %% ../nbs/02_shell.ipynb 8
4549
class CaptureShell(InteractiveShell):
4650
displayhook_class = _CustDisplayHook
4751

@@ -69,7 +73,7 @@ def set_path(self, path):
6973
if path.is_file(): path = path.parent
7074
self.run_cell(f"import sys; sys.path.insert(0, '{path.as_posix()}')")
7175

72-
# %% ../nbs/02_shell.ipynb 22
76+
# %% ../nbs/02_shell.ipynb 24
7377
def _out_stream(text, name): return dict(name=name, output_type='stream', text=text.splitlines(True))
7478
def _out_exc(e):
7579
ename = type(e).__name__
@@ -99,7 +103,7 @@ def _out_nb(o, fmt):
99103
res.append(_mk_out(*fmt.format(r), 'execute_result'))
100104
return res
101105

102-
# %% ../nbs/02_shell.ipynb 23
106+
# %% ../nbs/02_shell.ipynb 25
103107
@patch
104108
def run(self:CaptureShell,
105109
code:str, # Python/IPython code to run
@@ -111,7 +115,7 @@ def run(self:CaptureShell,
111115
self.exc = res.exception
112116
return _out_nb(res, self.display_formatter)
113117

114-
# %% ../nbs/02_shell.ipynb 37
118+
# %% ../nbs/02_shell.ipynb 39
115119
@patch
116120
def cell(self:CaptureShell, cell, stdout=True, stderr=True):
117121
"Run `cell`, skipping if not code, and store outputs back in cell"
@@ -123,32 +127,32 @@ def cell(self:CaptureShell, cell, stdout=True, stderr=True):
123127
for o in outs:
124128
if 'execution_count' in o: cell['execution_count'] = o['execution_count']
125129

126-
# %% ../nbs/02_shell.ipynb 40
130+
# %% ../nbs/02_shell.ipynb 42
127131
def find_output(outp, # Output from `run`
128132
ot='execute_result' # Output_type to find
129133
):
130134
"Find first output of type `ot` in `CaptureShell.run` output"
131135
return first(o for o in outp if o['output_type']==ot)
132136

133-
# %% ../nbs/02_shell.ipynb 43
137+
# %% ../nbs/02_shell.ipynb 45
134138
def out_exec(outp):
135139
"Get data from execution result in `outp`."
136140
out = find_output(outp)
137141
if out: return '\n'.join(first(out['data'].values()))
138142

139-
# %% ../nbs/02_shell.ipynb 45
143+
# %% ../nbs/02_shell.ipynb 47
140144
def out_stream(outp):
141145
"Get text from stream in `outp`."
142146
out = find_output(outp, 'stream')
143147
if out: return ('\n'.join(out['text'])).strip()
144148

145-
# %% ../nbs/02_shell.ipynb 47
149+
# %% ../nbs/02_shell.ipynb 49
146150
def out_error(outp):
147151
"Get traceback from error in `outp`."
148152
out = find_output(outp, 'error')
149153
if out: return '\n'.join(out['traceback'])
150154

151-
# %% ../nbs/02_shell.ipynb 49
155+
# %% ../nbs/02_shell.ipynb 51
152156
def _false(o): return False
153157

154158
@patch
@@ -168,7 +172,7 @@ def run_all(self:CaptureShell,
168172
postproc(cell)
169173
if self.exc and exc_stop: raise self.exc from None
170174

171-
# %% ../nbs/02_shell.ipynb 63
175+
# %% ../nbs/02_shell.ipynb 65
172176
@patch
173177
def execute(self:CaptureShell,
174178
src:str|Path, # Notebook path to read from
@@ -189,7 +193,7 @@ def execute(self:CaptureShell,
189193
inject_code=inject_code, inject_idx=inject_idx)
190194
if dest: write_nb(nb, dest)
191195

192-
# %% ../nbs/02_shell.ipynb 67
196+
# %% ../nbs/02_shell.ipynb 69
193197
@patch
194198
def prettytb(self:CaptureShell,
195199
fname:str|Path=None): # filename to print alongside the traceback
@@ -201,7 +205,7 @@ def prettytb(self:CaptureShell,
201205
fname_str = f' in {fname}' if fname else ''
202206
return f"{type(self.exc).__name__}{fname_str}:\n{_fence}\n{cell_str}\n"
203207

204-
# %% ../nbs/02_shell.ipynb 86
208+
# %% ../nbs/02_shell.ipynb 88
205209
@call_parse
206210
def exec_nb(
207211
src:str, # Notebook path to read from
@@ -214,3 +218,24 @@ def exec_nb(
214218
"Execute notebook from `src` and save with outputs to `dest`"
215219
CaptureShell().execute(src, dest, exc_stop=exc_stop, inject_code=inject_code,
216220
inject_path=inject_path, inject_idx=inject_idx)
221+
222+
# %% ../nbs/02_shell.ipynb 91
223+
class SmartCompleter(IPCompleter):
224+
def __init__(self, shell, namespace=None):
225+
if namespace is None: namespace = shell.user_ns
226+
super().__init__(shell, namespace)
227+
self.use_jedi = False
228+
229+
sdisp = StrDispatch()
230+
self.custom_completers = sdisp
231+
import_disp = CommandChainDispatcher()
232+
import_disp.add(types.MethodType(module_completer, shell))
233+
sdisp.add_s('import', import_disp)
234+
sdisp.add_s('from', import_disp)
235+
236+
def __call__(self, c):
237+
if not c: return []
238+
with provisionalcompleter():
239+
return [o.text.rpartition('.')[-1]
240+
for o in self.completions(c, len(c))
241+
if o.type=='<unknown>']

nbs/02_shell.ipynb

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,18 @@
3131
"from fastcore.utils import *\n",
3232
"from fastcore.script import call_parse\n",
3333
"\n",
34-
"import multiprocessing\n",
34+
"import multiprocessing,types\n",
3535
"try:\n",
3636
" if sys.platform == 'darwin': multiprocessing.set_start_method(\"fork\")\n",
3737
"except RuntimeError: pass # if re-running cell\n",
3838
"\n",
3939
"from IPython.core.interactiveshell import InteractiveShell, ExecutionInfo, ExecutionResult\n",
4040
"from IPython.core.displayhook import DisplayHook\n",
4141
"from IPython.utils.capture import capture_output\n",
42+
"from IPython.core.completer import IPCompleter,provisionalcompleter\n",
43+
"from IPython.core.hooks import CommandChainDispatcher\n",
44+
"from IPython.core.completerlib import module_completer\n",
45+
"from IPython.utils.strdispatch import StrDispatch\n",
4246
"from IPython.display import display as disp, HTML\n",
4347
"\n",
4448
"import traceback\n",
@@ -69,6 +73,61 @@
6973
"## CaptureShell -"
7074
]
7175
},
76+
{
77+
"cell_type": "code",
78+
"execution_count": null,
79+
"metadata": {},
80+
"outputs": [],
81+
"source": [
82+
"class SmartCompleter(IPCompleter):\n",
83+
" def __init__(self, shell, namespace=None):\n",
84+
" if namespace is None: namespace = shell.user_ns\n",
85+
" super().__init__(shell, namespace)\n",
86+
" self.use_jedi = False\n",
87+
"\n",
88+
" sdisp = StrDispatch()\n",
89+
" self.custom_completers = sdisp\n",
90+
" import_disp = CommandChainDispatcher()\n",
91+
" import_disp.add(types.MethodType(module_completer, shell))\n",
92+
" sdisp.add_s('import', import_disp)\n",
93+
" sdisp.add_s('from', import_disp)\n",
94+
"\n",
95+
" def __call__(self, c):\n",
96+
" if not c: return []\n",
97+
" with provisionalcompleter():\n",
98+
" return [o.text.rpartition('.')[-1]\n",
99+
" for o in self.completions(c, len(c))\n",
100+
" if o.type=='<unknown>']"
101+
]
102+
},
103+
{
104+
"cell_type": "code",
105+
"execution_count": null,
106+
"metadata": {},
107+
"outputs": [],
108+
"source": [
109+
"cc = SmartCompleter(get_ipython())\n",
110+
"\n",
111+
"def test_set(a,b): return test_eq(set(a), set(b))\n",
112+
"\n",
113+
"class _f:\n",
114+
" def __init__(self): self.bar,self.baz,self.room = 0,0,0\n",
115+
"\n",
116+
"foo = _f()\n",
117+
"\n",
118+
"test_set(cc(\"b\"), ['bin', 'bool', 'break', 'breakpoint', 'bytearray', 'bytes'])\n",
119+
"test_set(cc(\"foo.b\"), ['bar', 'baz'])\n",
120+
"test_set(cc(\"x=1; x = foo.b\"), ['bar', 'baz'])\n",
121+
"test_set(cc(\"ab\"), ['abs'])\n",
122+
"test_set(cc(\"b = ab\"), ['abs'])\n",
123+
"test_set(cc(\"\"), [])\n",
124+
"test_set(cc(\"foo.\"), ['bar', 'baz', 'room'])\n",
125+
"test_set(cc(\"nonexistent.b\"), [])\n",
126+
"test_set(cc(\"foo.nonexistent.b\"), [])\n",
127+
"test_set(cc(\"import ab\"), ['abc'])\n",
128+
"test_set(cc(\"from abc import AB\"), ['ABC', 'ABCMeta'])"
129+
]
130+
},
72131
{
73132
"cell_type": "code",
74133
"execution_count": null,
@@ -1635,6 +1694,69 @@
16351694
"This is the command-line version of `CaptureShell.execute`. Run `exec_nb -h` from the command line to see how to pass arguments. If you don't pass `dest` then the output notebook won't be saved; this is mainly useful for running tests."
16361695
]
16371696
},
1697+
{
1698+
"cell_type": "markdown",
1699+
"metadata": {},
1700+
"source": [
1701+
"## Completions"
1702+
]
1703+
},
1704+
{
1705+
"cell_type": "code",
1706+
"execution_count": null,
1707+
"metadata": {},
1708+
"outputs": [],
1709+
"source": [
1710+
"#| export\n",
1711+
"class SmartCompleter(IPCompleter):\n",
1712+
" def __init__(self, shell, namespace=None):\n",
1713+
" if namespace is None: namespace = shell.user_ns\n",
1714+
" super().__init__(shell, namespace)\n",
1715+
" self.use_jedi = False\n",
1716+
"\n",
1717+
" sdisp = StrDispatch()\n",
1718+
" self.custom_completers = sdisp\n",
1719+
" import_disp = CommandChainDispatcher()\n",
1720+
" import_disp.add(types.MethodType(module_completer, shell))\n",
1721+
" sdisp.add_s('import', import_disp)\n",
1722+
" sdisp.add_s('from', import_disp)\n",
1723+
"\n",
1724+
" def __call__(self, c):\n",
1725+
" if not c: return []\n",
1726+
" with provisionalcompleter():\n",
1727+
" return [o.text.rpartition('.')[-1]\n",
1728+
" for o in self.completions(c, len(c))\n",
1729+
" if o.type=='<unknown>']"
1730+
]
1731+
},
1732+
{
1733+
"cell_type": "code",
1734+
"execution_count": null,
1735+
"metadata": {},
1736+
"outputs": [],
1737+
"source": [
1738+
"cc = SmartCompleter(get_ipython())\n",
1739+
"\n",
1740+
"def test_set(a,b): return test_eq(set(a), set(b))\n",
1741+
"\n",
1742+
"class _f:\n",
1743+
" def __init__(self): self.bar,self.baz,self.room = 0,0,0\n",
1744+
"\n",
1745+
"foo = _f()\n",
1746+
"\n",
1747+
"assert set(cc(\"b\")).issuperset(['bin', 'bool', 'break', 'breakpoint', 'bytearray', 'bytes'])\n",
1748+
"test_set(cc(\"foo.b\"), ['bar', 'baz'])\n",
1749+
"test_set(cc(\"x=1; x = foo.b\"), ['bar', 'baz'])\n",
1750+
"test_set(cc(\"ab\"), ['abs'])\n",
1751+
"test_set(cc(\"b = ab\"), ['abs'])\n",
1752+
"test_set(cc(\"\"), [])\n",
1753+
"test_set(cc(\"foo.\"), ['bar', 'baz', 'room'])\n",
1754+
"test_set(cc(\"nonexistent.b\"), [])\n",
1755+
"test_set(cc(\"foo.nonexistent.b\"), [])\n",
1756+
"test_set(cc(\"import ab\"), ['abc'])\n",
1757+
"test_set(cc(\"from abc import AB\"), ['ABC', 'ABCMeta'])"
1758+
]
1759+
},
16381760
{
16391761
"cell_type": "markdown",
16401762
"metadata": {},

0 commit comments

Comments
 (0)