From 31bcb1882861141bfe5ec97a0549de85c1bb4970 Mon Sep 17 00:00:00 2001 From: artste Date: Sat, 13 May 2023 17:50:33 +0200 Subject: [PATCH 1/3] FEAT: support functions definitions inside cell --- nbs/01_ast.ipynb | 21 ++++++++++++++++++++- testcell/core.py | 22 ++++++++++++---------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/nbs/01_ast.ipynb b/nbs/01_ast.ipynb index 87e3996..b1dd798 100644 --- a/nbs/01_ast.ipynb +++ b/nbs/01_ast.ipynb @@ -57,8 +57,10 @@ "#| export\n", "def last_node(code):\n", " tree = ast.parse(code)\n", + " if len(tree.body)==0: return None\n", + " src = tree.body[-1]\n", " last_node = None\n", - " for node in ast.walk(tree):\n", + " for node in ast.walk(src):\n", " if isinstance(node, ast.stmt):\n", " last_node = node\n", " return last_node" @@ -100,6 +102,23 @@ "test_eq(node_source(last_node(sample_code),sample_code), 'c = a+b')" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| test\n", + "sample_code = '''\n", + "def my_function(x):\n", + " print(aaa)\n", + " return x\n", + " \n", + "my_function(123)\n", + "'''\n", + "test_eq(node_source(last_node(sample_code),sample_code), 'my_function(123)')" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/testcell/core.py b/testcell/core.py index 2ff6268..2880440 100644 --- a/testcell/core.py +++ b/testcell/core.py @@ -10,8 +10,10 @@ # %% ../nbs/01_ast.ipynb 5 def last_node(code): tree = ast.parse(code) + if len(tree.body)==0: return None + src = tree.body[-1] last_node = None - for node in ast.walk(tree): + for node in ast.walk(src): if isinstance(node, ast.stmt): last_node = node return last_node @@ -20,11 +22,11 @@ def last_node(code): def node_source(node,code): return ast.get_source_segment(code,node) -# %% ../nbs/01_ast.ipynb 9 +# %% ../nbs/01_ast.ipynb 10 def is_assignment(node): return isinstance(node, ast.Assign) -# %% ../nbs/01_ast.ipynb 11 +# %% ../nbs/01_ast.ipynb 12 def extract_call(node): if not isinstance(node, ast.Expr): return None node = node.value # step in @@ -35,24 +37,24 @@ def extract_call(node): if isinstance(n, ast.Attribute): return n.attr return None # all the rest is not supported -# %% ../nbs/01_ast.ipynb 13 +# %% ../nbs/01_ast.ipynb 14 def is_function_call(node,names): function_name = extract_call(node) if function_name is None: return False # this is not a function call return function_name in names -# %% ../nbs/01_ast.ipynb 15 +# %% ../nbs/01_ast.ipynb 16 def is_import_statement(node): return isinstance(node, (ast.Import, ast.ImportFrom)) -# %% ../nbs/01_ast.ipynb 17 +# %% ../nbs/01_ast.ipynb 18 def need_display(node): if node is None: return False if is_function_call(node,names=['print','display']): return False if is_import_statement(node): return False return True -# %% ../nbs/01_ast.ipynb 19 +# %% ../nbs/01_ast.ipynb 20 def wrap_node(node,function_name): return ast.Expr( value=ast.Call( @@ -61,13 +63,13 @@ def wrap_node(node,function_name): keywords=[]) ) -# %% ../nbs/01_ast.ipynb 22 +# %% ../nbs/01_ast.ipynb 23 def last_statement_has_semicolon(code): t = [x.strip() for x in code.splitlines()] t = [x for x in t if not x.startswith('#')] return t[-1].endswith(';') -# %% ../nbs/01_ast.ipynb 24 +# %% ../nbs/01_ast.ipynb 25 def code_till_node(code:str,node): t = code.splitlines() t = t[:node.lineno] @@ -75,7 +77,7 @@ def code_till_node(code:str,node): if len(t[-1])==0: t = t[:-1] return '\n'.join(t) -# %% ../nbs/01_ast.ipynb 27 +# %% ../nbs/01_ast.ipynb 28 def auto_display(code): if last_statement_has_semicolon(code): return code From 0189abdb8a15992310c6c66941083f3a4109688e Mon Sep 17 00:00:00 2001 From: artste Date: Sat, 13 May 2023 17:51:19 +0200 Subject: [PATCH 2/3] Bump version --- settings.ini | 2 +- testcell/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/settings.ini b/settings.ini index 92344ca..37d3da6 100644 --- a/settings.ini +++ b/settings.ini @@ -5,7 +5,7 @@ ### Python library ### repo = testcell lib_name = %(repo)s -version = 0.0.3 +version = 0.0.4 min_python = 3.7 license = apache2 black_formatting = False diff --git a/testcell/__init__.py b/testcell/__init__.py index 094d155..f768fc7 100644 --- a/testcell/__init__.py +++ b/testcell/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.0.3" +__version__ = "0.0.4" # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/02_testcell.ipynb. # %% auto 0 From 1075d82f1140f550cb39e19983e90d6c3ab64ee7 Mon Sep 17 00:00:00 2001 From: artste Date: Sat, 13 May 2023 18:01:36 +0200 Subject: [PATCH 3/3] DOC: extended example on usage with functions --- README.md | 31 +++++++++++++++++ nbs/index.ipynb | 93 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/README.md b/README.md index 8aeaa90..aeee03e 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,37 @@ available: + `__builtins__` : built in python’s functions. + `_test_cell_` : `%%testcell` wrapped function executed (we can’t avoid this). +``` python +%%testcell +def my_function(x): + print(aaa) # global variable + return x + +try: + my_function(123) +except Exception as e: + print(e) +``` + + global variable + +``` python +%%testcelln +def my_function(x): + print(aaa) # global variable + return x + +try: + my_function(123) +except Exception as e: + print(e) +``` + + name 'aaa' is not defined + +As you can see from this last example, `%%testcelln` helps you to +identify that `my_function` refers global variable `aaa`. + **IMPORTANT**: this is *just wrapping your cell* and so it’s still running on your main kernel. If you modify variables that has been created outside of this cell (aka: if you have side effects) this will diff --git a/nbs/index.ipynb b/nbs/index.ipynb index a251bff..4d953a3 100644 --- a/nbs/index.ipynb +++ b/nbs/index.ipynb @@ -474,6 +474,99 @@ "+ `_test_cell_` : `%%testcell` wrapped function executed (we can't avoid this)." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "%%testcell\n", + "def my_function(x):\n", + " print(aaa) # global variable\n", + " return x\n", + "\n", + "try:\n", + " my_function(123)\n", + "except Exception as e:\n", + " print(e)\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "global variable\n" + ] + } + ], + "source": [ + "%%testcell\n", + "#| echo: false\n", + "def my_function(x):\n", + " print(aaa) # global variable\n", + " return x\n", + "\n", + "try:\n", + " my_function(123)\n", + "except Exception as e:\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "%%testcelln\n", + "def my_function(x):\n", + " print(aaa) # global variable\n", + " return x\n", + "\n", + "try:\n", + " my_function(123)\n", + "except Exception as e:\n", + " print(e)\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "name 'aaa' is not defined\n" + ] + } + ], + "source": [ + "%%testcelln\n", + "#| echo: false\n", + "def my_function(x):\n", + " print(aaa) # global variable\n", + " return x\n", + "\n", + "try:\n", + " my_function(123)\n", + "except Exception as e:\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see from this last example, `%%testcelln` helps you to identify that `my_function` refers global variable `aaa`." + ] + }, { "cell_type": "markdown", "metadata": {},