Skip to content

Commit 1a1d8db

Browse files
committed
fixes #28
1 parent a579df6 commit 1a1d8db

File tree

2 files changed

+110
-22
lines changed

2 files changed

+110
-22
lines changed

01_funccall.ipynb

Lines changed: 96 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,12 +1368,12 @@
13681368
{
13691369
"cell_type": "code",
13701370
"execution_count": null,
1371-
"id": "84cadbec-0b34-4669-bfc9-6bf790816035",
1371+
"id": "1574585f",
13721372
"metadata": {},
13731373
"outputs": [],
13741374
"source": [
13751375
"#| exports\n",
1376-
"def _run(code:str ):\n",
1376+
"def _run(code:str, glb:dict=None, loc:dict=None):\n",
13771377
" \"Run `code`, returning final expression (similar to IPython)\"\n",
13781378
" tree = ast.parse(code)\n",
13791379
" last_node = tree.body[-1] if tree.body else None\n",
@@ -1385,13 +1385,13 @@
13851385
" tree.body[-1] = _copy_loc(assign_node, last_node)\n",
13861386
"\n",
13871387
" compiled_code = compile(tree, filename='<ast>', mode='exec')\n",
1388-
" namespace = {}\n",
1388+
" glb = glb or {}\n",
13891389
" stdout_buffer = io.StringIO()\n",
13901390
" saved_stdout = sys.stdout\n",
13911391
" sys.stdout = stdout_buffer\n",
1392-
" try: exec(compiled_code, namespace)\n",
1392+
" try: exec(compiled_code, glb, loc)\n",
13931393
" finally: sys.stdout = saved_stdout\n",
1394-
" _result = namespace.get('_result', None)\n",
1394+
" _result = glb.get('_result', None)\n",
13951395
" if _result is not None: return _result\n",
13961396
" return stdout_buffer.getvalue().strip()"
13971397
]
@@ -1462,15 +1462,19 @@
14621462
"outputs": [],
14631463
"source": [
14641464
"#| exports\n",
1465-
"def python(code, # Code to execute\n",
1466-
" timeout=5 # Maximum run time in seconds before a `TimeoutError` is raised\n",
1465+
"def python(code:str, # Code to execute\n",
1466+
" glb:Optional[dict]=None, # Globals namespace\n",
1467+
" loc:Optional[dict]=None, # Locals namespace\n",
1468+
" timeout:int=3600 # Maximum run time in seconds before a `TimeoutError` is raised\n",
14671469
" ): # Result of last node, if it's an expression, or `None` otherwise\n",
14681470
" \"\"\"Executes python `code` with `timeout` and returning final expression (similar to IPython).\n",
14691471
" Raised exceptions are returned as a string, with a stack trace.\"\"\"\n",
14701472
" def handler(*args): raise TimeoutError()\n",
1473+
" if glb is None: glb = inspect.currentframe().f_back.f_globals\n",
1474+
" if loc is None: loc=glb\n",
14711475
" signal.signal(signal.SIGALRM, handler)\n",
14721476
" signal.alarm(timeout)\n",
1473-
" try: return _run(code)\n",
1477+
" try: return _run(code, glb, loc)\n",
14741478
" except Exception as e: return traceback.format_exc()\n",
14751479
" finally: signal.alarm(0)"
14761480
]
@@ -1512,18 +1516,98 @@
15121516
"id": "6c629442",
15131517
"metadata": {},
15141518
"source": [
1515-
"If the code takes longer than `timeout` then it raises a `TimeoutError`."
1519+
"If the code takes longer than `timeout` then it returns an error string."
15161520
]
15171521
},
15181522
{
15191523
"cell_type": "code",
15201524
"execution_count": null,
15211525
"id": "fcb472b3",
15221526
"metadata": {},
1523-
"outputs": [],
1527+
"outputs": [
1528+
{
1529+
"name": "stdout",
1530+
"output_type": "stream",
1531+
"text": [
1532+
"Traceback (most recent call last):\n",
1533+
" File \"/var/folders/51/b2_szf2945n072c0vj2cyty40000gn/T/ipykernel_12053/775515280.py\", line 12, in python\n",
1534+
" try: return _run(code, glb, loc)\n",
1535+
" ^^^^^^^^^^^^^^^^^^^^\n",
1536+
" File \"/var/folders/51/b2_szf2945n072c0vj2cyty40000gn/T/ipykernel_12053/1858893181.py\", line 18, in _run\n",
1537+
" try: exec(compiled_code, glb, loc)\n",
1538+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
1539+
" File \"<ast>\", line 1, in <module>\n",
1540+
" File \"/var/folders/51/b2_szf2945n072c0vj2cyty40000gn/T/ipykernel_12053/775515280.py\", line 9, in handler\n",
1541+
" def handler(*args): raise TimeoutError()\n",
1542+
" ^^^^^^^^^^^^^^^^^^^^\n",
1543+
"TimeoutError\n",
1544+
"\n"
1545+
]
1546+
}
1547+
],
1548+
"source": [
1549+
"print(python('import time; time.sleep(10)', timeout=1))"
1550+
]
1551+
},
1552+
{
1553+
"cell_type": "markdown",
1554+
"id": "d45684c1",
1555+
"metadata": {},
1556+
"source": [
1557+
"By default the caller's global namespace is used."
1558+
]
1559+
},
1560+
{
1561+
"cell_type": "code",
1562+
"execution_count": null,
1563+
"id": "72dfe290",
1564+
"metadata": {},
1565+
"outputs": [
1566+
{
1567+
"data": {
1568+
"text/plain": [
1569+
"1"
1570+
]
1571+
},
1572+
"execution_count": null,
1573+
"metadata": {},
1574+
"output_type": "execute_result"
1575+
}
1576+
],
1577+
"source": [
1578+
"python(\"a=1\")\n",
1579+
"a"
1580+
]
1581+
},
1582+
{
1583+
"cell_type": "markdown",
1584+
"id": "bf48557c",
1585+
"metadata": {},
1586+
"source": [
1587+
"Pass a different `glb` if needed."
1588+
]
1589+
},
1590+
{
1591+
"cell_type": "code",
1592+
"execution_count": null,
1593+
"id": "55fb5613",
1594+
"metadata": {},
1595+
"outputs": [
1596+
{
1597+
"data": {
1598+
"text/plain": [
1599+
"(1, 3)"
1600+
]
1601+
},
1602+
"execution_count": null,
1603+
"metadata": {},
1604+
"output_type": "execute_result"
1605+
}
1606+
],
15241607
"source": [
1525-
"try: python('import time; time.sleep(10)', timeout=1)\n",
1526-
"except TimeoutError: print('Timed out')"
1608+
"glb = {}\n",
1609+
"python(\"a=3\", glb)\n",
1610+
"a, glb['a']"
15271611
]
15281612
},
15291613
{

toolslm/funccall.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def _copy_loc(new, orig):
141141
return new
142142

143143
# %% ../01_funccall.ipynb 69
144-
def _run(code:str ):
144+
def _run(code:str, glb:dict=None, loc:dict=None):
145145
"Run `code`, returning final expression (similar to IPython)"
146146
tree = ast.parse(code)
147147
last_node = tree.body[-1] if tree.body else None
@@ -153,30 +153,34 @@ def _run(code:str ):
153153
tree.body[-1] = _copy_loc(assign_node, last_node)
154154

155155
compiled_code = compile(tree, filename='<ast>', mode='exec')
156-
namespace = {}
156+
glb = glb or {}
157157
stdout_buffer = io.StringIO()
158158
saved_stdout = sys.stdout
159159
sys.stdout = stdout_buffer
160-
try: exec(compiled_code, namespace)
160+
try: exec(compiled_code, glb, loc)
161161
finally: sys.stdout = saved_stdout
162-
_result = namespace.get('_result', None)
162+
_result = glb.get('_result', None)
163163
if _result is not None: return _result
164164
return stdout_buffer.getvalue().strip()
165165

166166
# %% ../01_funccall.ipynb 74
167-
def python(code, # Code to execute
168-
timeout=5 # Maximum run time in seconds before a `TimeoutError` is raised
167+
def python(code:str, # Code to execute
168+
glb:Optional[dict]=None, # Globals namespace
169+
loc:Optional[dict]=None, # Locals namespace
170+
timeout:int=3600 # Maximum run time in seconds before a `TimeoutError` is raised
169171
): # Result of last node, if it's an expression, or `None` otherwise
170172
"""Executes python `code` with `timeout` and returning final expression (similar to IPython).
171173
Raised exceptions are returned as a string, with a stack trace."""
172174
def handler(*args): raise TimeoutError()
175+
if glb is None: glb = inspect.currentframe().f_back.f_globals
176+
if loc is None: loc=glb
173177
signal.signal(signal.SIGALRM, handler)
174178
signal.alarm(timeout)
175-
try: return _run(code)
179+
try: return _run(code, glb, loc)
176180
except Exception as e: return traceback.format_exc()
177181
finally: signal.alarm(0)
178182

179-
# %% ../01_funccall.ipynb 81
183+
# %% ../01_funccall.ipynb 85
180184
def mk_ns(*funcs_or_objs):
181185
merged = {}
182186
for o in funcs_or_objs:
@@ -185,14 +189,14 @@ def mk_ns(*funcs_or_objs):
185189
if callable(o) and hasattr(o, '__name__'): merged |= {o.__name__: o}
186190
return merged
187191

188-
# %% ../01_funccall.ipynb 90
192+
# %% ../01_funccall.ipynb 94
189193
def call_func(fc_name, fc_inputs, ns):
190194
"Call the function `fc_name` with the given `fc_inputs` using namespace `ns`."
191195
if not isinstance(ns, abc.Mapping): ns = mk_ns(*ns)
192196
func = ns[fc_name]
193197
return func(**fc_inputs)
194198

195-
# %% ../01_funccall.ipynb 97
199+
# %% ../01_funccall.ipynb 101
196200
async def call_func_async(fc_name, fc_inputs, ns):
197201
"Awaits the function `fc_name` with the given `fc_inputs` using namespace `ns`."
198202
if not isinstance(ns, abc.Mapping): ns = mk_ns(*ns)

0 commit comments

Comments
 (0)