Skip to content

Commit 3e81c35

Browse files
authored
Merge pull request #63 from AnswerDotAI/async-shell-dot-run
add `shell.run_async`
2 parents 4e0566a + 209ba4c commit 3e81c35

File tree

3 files changed

+144
-10
lines changed

3 files changed

+144
-10
lines changed

execnb/_modidx.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
'execnb.shell.CaptureShell.prettytb': ('shell.html#captureshell.prettytb', 'execnb/shell.py'),
3030
'execnb.shell.CaptureShell.run': ('shell.html#captureshell.run', 'execnb/shell.py'),
3131
'execnb.shell.CaptureShell.run_all': ('shell.html#captureshell.run_all', 'execnb/shell.py'),
32+
'execnb.shell.CaptureShell.run_async': ('shell.html#captureshell.run_async', 'execnb/shell.py'),
3233
'execnb.shell.CaptureShell.run_cell': ('shell.html#captureshell.run_cell', 'execnb/shell.py'),
3334
'execnb.shell.CaptureShell.set_path': ('shell.html#captureshell.set_path', 'execnb/shell.py'),
3435
'execnb.shell.ExecutionInfo.__repr__': ('shell.html#executioninfo.__repr__', 'execnb/shell.py'),

execnb/shell.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def __repr__(self: ExecutionResult): return f'result: {self.result}; err: {self.
5353
class CaptureShell(InteractiveShell):
5454
displayhook_class = _CustDisplayHook
5555

56-
def __init__(self, path:str|Path=None, mpl_format='retina', history=False, timeout=None):
56+
def __init__(self, path:str|Path=None, mpl_format='retina', history=False, timeout:Optional[int]=None):
5757
super().__init__()
5858
self.history_manager.enabled = history
5959
self.timeout = timeout
@@ -67,7 +67,7 @@ def __init__(self, path:str|Path=None, mpl_format='retina', history=False, timeo
6767
self.run_cell(f"set_matplotlib_formats('{mpl_format}')")
6868

6969
def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=True, cell_id=None,
70-
stdout=True, stderr=True, display=True, timeout=None):
70+
stdout=True, stderr=True, display=True, timeout:Optional[int]=None):
7171
if not timeout: timeout = self.timeout
7272
# TODO what if there's a comment?
7373
semic = raw_cell.rstrip().endswith(';')
@@ -129,13 +129,23 @@ def _out_nb(o, fmt):
129129
def run(self:CaptureShell,
130130
code:str, # Python/IPython code to run
131131
stdout=True, # Capture stdout and save as output?
132-
stderr=True): # Capture stderr and save as output?
132+
stderr=True, # Capture stderr and save as output?
133+
timeout:Optional[int]=None): # Shell command will time out after {timeout} seconds
133134
"Run `code`, returning a list of all outputs in Jupyter notebook format"
134-
res = self.run_cell(code, stdout=stdout, stderr=stderr)
135+
res = self.run_cell(code, stdout=stdout, stderr=stderr, timeout=timeout)
135136
self.result = res.result.result
136137
self.exc = res.exception
137138
return _out_nb(res, self.display_formatter)
138139

140+
# %% ../nbs/02_shell.ipynb
141+
@patch
142+
async def run_async(self:CaptureShell,
143+
code: str, # Python/IPython code to run
144+
stdout=True, # Capture stdout and save as output?
145+
stderr=True, # Capture stderr and save as output?
146+
timeout:Optional[int]=None): # Shell command will time out after {timeout} seconds
147+
return self.run(code, stdout=stdout, stderr=stderr, timeout=timeout)
148+
139149
# %% ../nbs/02_shell.ipynb
140150
def _pre(s, xtra=''): return f"<pre {xtra}><code>{escape(s)}</code></pre>"
141151
def _strip(s): return strip_ansi(escape(s))

nbs/02_shell.ipynb

Lines changed: 129 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
"class CaptureShell(InteractiveShell):\n",
106106
" displayhook_class = _CustDisplayHook\n",
107107
"\n",
108-
" def __init__(self, path:str|Path=None, mpl_format='retina', history=False, timeout=None):\n",
108+
" def __init__(self, path:str|Path=None, mpl_format='retina', history=False, timeout:Optional[int]=None):\n",
109109
" super().__init__()\n",
110110
" self.history_manager.enabled = history\n",
111111
" self.timeout = timeout\n",
@@ -119,7 +119,7 @@
119119
" self.run_cell(f\"set_matplotlib_formats('{mpl_format}')\")\n",
120120
"\n",
121121
" def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=True, cell_id=None,\n",
122-
" stdout=True, stderr=True, display=True, timeout=None):\n",
122+
" stdout=True, stderr=True, display=True, timeout:Optional[int]=None):\n",
123123
" if not timeout: timeout = self.timeout\n",
124124
" # TODO what if there's a comment?\n",
125125
" semic = raw_cell.rstrip().endswith(';')\n",
@@ -706,9 +706,10 @@
706706
"def run(self:CaptureShell,\n",
707707
" code:str, # Python/IPython code to run\n",
708708
" stdout=True, # Capture stdout and save as output?\n",
709-
" stderr=True): # Capture stderr and save as output?\n",
709+
" stderr=True, # Capture stderr and save as output?\n",
710+
" timeout:Optional[int]=None): # Shell command will time out after {timeout} seconds\n",
710711
" \"Run `code`, returning a list of all outputs in Jupyter notebook format\"\n",
711-
" res = self.run_cell(code, stdout=stdout, stderr=stderr)\n",
712+
" res = self.run_cell(code, stdout=stdout, stderr=stderr, timeout=timeout)\n",
712713
" self.result = res.result.result\n",
713714
" self.exc = res.exception\n",
714715
" return _out_nb(res, self.display_formatter)"
@@ -863,6 +864,128 @@
863864
"o"
864865
]
865866
},
867+
{
868+
"cell_type": "code",
869+
"execution_count": null,
870+
"metadata": {},
871+
"outputs": [
872+
{
873+
"data": {
874+
"text/plain": [
875+
"[{'name': 'stdout', 'output_type': 'stream', 'text': ['no timeout\\n']}]"
876+
]
877+
},
878+
"execution_count": null,
879+
"metadata": {},
880+
"output_type": "execute_result"
881+
}
882+
],
883+
"source": [
884+
"s.run(\"import time; time.sleep(0.1); print('no timeout')\", timeout=1)"
885+
]
886+
},
887+
{
888+
"cell_type": "code",
889+
"execution_count": null,
890+
"metadata": {},
891+
"outputs": [
892+
{
893+
"data": {
894+
"text/plain": [
895+
"['\\x1b[0;31m---------------------------------------------------------------------------\\x1b[0m\\n',\n",
896+
" '\\x1b[0;31mTimeoutError\\x1b[0m Traceback (most recent call last)\\n']"
897+
]
898+
},
899+
"execution_count": null,
900+
"metadata": {},
901+
"output_type": "execute_result"
902+
}
903+
],
904+
"source": [
905+
"o = s.run(\"import time; time.sleep(1.1)\", timeout=1)\n",
906+
"o[0]['text'][:2]"
907+
]
908+
},
909+
{
910+
"cell_type": "code",
911+
"execution_count": null,
912+
"metadata": {},
913+
"outputs": [],
914+
"source": [
915+
"#| export\n",
916+
"@patch\n",
917+
"async def run_async(self:CaptureShell,\n",
918+
" code: str, # Python/IPython code to run\n",
919+
" stdout=True, # Capture stdout and save as output?\n",
920+
" stderr=True, # Capture stderr and save as output?\n",
921+
" timeout:Optional[int]=None): # Shell command will time out after {timeout} seconds\n",
922+
" return self.run(code, stdout=stdout, stderr=stderr, timeout=timeout)"
923+
]
924+
},
925+
{
926+
"cell_type": "code",
927+
"execution_count": null,
928+
"metadata": {},
929+
"outputs": [
930+
{
931+
"data": {
932+
"text/plain": [
933+
"[{'data': {'text/plain': ['2']},\n",
934+
" 'metadata': {},\n",
935+
" 'output_type': 'execute_result'}]"
936+
]
937+
},
938+
"execution_count": null,
939+
"metadata": {},
940+
"output_type": "execute_result"
941+
}
942+
],
943+
"source": [
944+
"await s.run_async(\"1+1\")"
945+
]
946+
},
947+
{
948+
"cell_type": "code",
949+
"execution_count": null,
950+
"metadata": {},
951+
"outputs": [
952+
{
953+
"data": {
954+
"text/plain": [
955+
"[{'name': 'stdout', 'output_type': 'stream', 'text': ['no timeout\\n']}]"
956+
]
957+
},
958+
"execution_count": null,
959+
"metadata": {},
960+
"output_type": "execute_result"
961+
}
962+
],
963+
"source": [
964+
"await s.run_async(\"import time; time.sleep(0.1); print('no timeout')\", timeout=1)"
965+
]
966+
},
967+
{
968+
"cell_type": "code",
969+
"execution_count": null,
970+
"metadata": {},
971+
"outputs": [
972+
{
973+
"data": {
974+
"text/plain": [
975+
"['\\x1b[0;31m---------------------------------------------------------------------------\\x1b[0m\\n',\n",
976+
" '\\x1b[0;31mTimeoutError\\x1b[0m Traceback (most recent call last)\\n']"
977+
]
978+
},
979+
"execution_count": null,
980+
"metadata": {},
981+
"output_type": "execute_result"
982+
}
983+
],
984+
"source": [
985+
"await s.run_async(\"import time; time.sleep(1.1);\", timeout=1)\n",
986+
"o[0]['text'][:2]"
987+
]
988+
},
866989
{
867990
"cell_type": "code",
868991
"execution_count": null,
@@ -2011,9 +2134,9 @@
20112134
],
20122135
"metadata": {
20132136
"kernelspec": {
2014-
"display_name": "python3",
2137+
"display_name": "env",
20152138
"language": "python",
2016-
"name": "python3"
2139+
"name": "env"
20172140
}
20182141
},
20192142
"nbformat": 4,

0 commit comments

Comments
 (0)