From ebe9e178a3e0f66d81ab653fdf623424f72da938 Mon Sep 17 00:00:00 2001 From: Stefano Giomo Date: Mon, 3 Nov 2025 16:30:47 +0000 Subject: [PATCH 01/18] Improved code rendering in notebook --- nbs/02_testcell.ipynb | 935 ++++++++++++++++++++++++++---------------- 1 file changed, 589 insertions(+), 346 deletions(-) diff --git a/nbs/02_testcell.ipynb b/nbs/02_testcell.ipynb index 90989da..76e3907 100644 --- a/nbs/02_testcell.ipynb +++ b/nbs/02_testcell.ipynb @@ -41,8 +41,15 @@ "#| export\n", "import ast\n", "from IPython.core.magic import register_cell_magic, needs_local_scope\n", - "from IPython import get_ipython # needed for quarto\n", - "from IPython.display import Code" + "from IPython import get_ipython # needed for quarto" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "WARNING: the official `IPython.display.Code` syntax hilighter don't seems to work. We're creating a \"drop-in\" replacement that force `full=True` in HtmlFormatter. This seems to work properly and give us more control on code display.\n", + "For details see: https://github.com/ipython/ipython/blob/72bb67ee8f57cb347ba358cce786c3fa87c470b9/IPython/lib/display.py#L667" ] }, { @@ -201,6 +208,198 @@ "[print(o.__repr__()) for o in [skip_message_box,testcell_message_box,noglobals_message_box]];" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "from pygments.formatters import HtmlFormatter\n", + "from pygments import highlight\n", + "from pygments.lexers import PythonLexer\n", + "from IPython.display import HTML\n", + "\n", + "#class Code(data=None, url=None, filename=None, language=None):\n", + "# return HTML(highlight(data, PythonLexer(), HtmlFormatter(full=True)))\n", + "\n", + "class Code:\n", + " def __init__(self, data=None, url=None, filename=None, language=None):\n", + " self.data = data\n", + " # NOTE: skipping other arguments, we're keeling them only for backward compatibility\n", + " def _repr_html_(self): return highlight(self.data, PythonLexer(), HtmlFormatter(full=True))\n", + " def __repr__(self): return self.data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
# This is a comment\n",
+       "class A:\n",
+       "   def b(self): return 1\n",
+       "\n",
+       "def c(): return 3\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "\n", + "# This is a comment\n", + "class A:\n", + " def b(self): return 1\n", + "\n", + "def c(): return 3" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# test rendering inside jupyter\n", + "Code('''\n", + "# This is a comment\n", + "class A:\n", + " def b(self): return 1\n", + "\n", + "def c(): return 3\n", + "''')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "# This is a comment\n", + "class A:\n", + " def b(self): return 1\n", + "\n", + "def c(): return 3\n", + "\n" + ] + } + ], + "source": [ + "# test rendering in console\n", + "print(Code('''\n", + "# This is a comment\n", + "class A:\n", + " def b(self): return 1\n", + "\n", + "def c(): return 3\n", + "''').__repr__())" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -747,80 +946,104 @@ { "data": { "text/html": [ - "
### BEGIN\n",
+       "body .hll { background-color: #ffffcc }\n",
+       "body { background: #f8f8f8; }\n",
+       "body .c { color: #3D7B7B; font-style: italic } /* Comment */\n",
+       "body .err { border: 1px solid #FF0000 } /* Error */\n",
+       "body .k { color: #008000; font-weight: bold } /* Keyword */\n",
+       "body .o { color: #666666 } /* Operator */\n",
+       "body .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n",
+       "body .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n",
+       "body .cp { color: #9C6500 } /* Comment.Preproc */\n",
+       "body .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n",
+       "body .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n",
+       "body .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n",
+       "body .gd { color: #A00000 } /* Generic.Deleted */\n",
+       "body .ge { font-style: italic } /* Generic.Emph */\n",
+       "body .gr { color: #E40000 } /* Generic.Error */\n",
+       "body .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
+       "body .gi { color: #008400 } /* Generic.Inserted */\n",
+       "body .go { color: #717171 } /* Generic.Output */\n",
+       "body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
+       "body .gs { font-weight: bold } /* Generic.Strong */\n",
+       "body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
+       "body .gt { color: #0044DD } /* Generic.Traceback */\n",
+       "body .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
+       "body .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
+       "body .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
+       "body .kp { color: #008000 } /* Keyword.Pseudo */\n",
+       "body .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
+       "body .kt { color: #B00040 } /* Keyword.Type */\n",
+       "body .m { color: #666666 } /* Literal.Number */\n",
+       "body .s { color: #BA2121 } /* Literal.String */\n",
+       "body .na { color: #687822 } /* Name.Attribute */\n",
+       "body .nb { color: #008000 } /* Name.Builtin */\n",
+       "body .nc { color: #0000FF; font-weight: bold } /* Name.Class */\n",
+       "body .no { color: #880000 } /* Name.Constant */\n",
+       "body .nd { color: #AA22FF } /* Name.Decorator */\n",
+       "body .ni { color: #717171; font-weight: bold } /* Name.Entity */\n",
+       "body .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n",
+       "body .nf { color: #0000FF } /* Name.Function */\n",
+       "body .nl { color: #767600 } /* Name.Label */\n",
+       "body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */\n",
+       "body .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
+       "body .nv { color: #19177C } /* Name.Variable */\n",
+       "body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */\n",
+       "body .w { color: #bbbbbb } /* Text.Whitespace */\n",
+       "body .mb { color: #666666 } /* Literal.Number.Bin */\n",
+       "body .mf { color: #666666 } /* Literal.Number.Float */\n",
+       "body .mh { color: #666666 } /* Literal.Number.Hex */\n",
+       "body .mi { color: #666666 } /* Literal.Number.Integer */\n",
+       "body .mo { color: #666666 } /* Literal.Number.Oct */\n",
+       "body .sa { color: #BA2121 } /* Literal.String.Affix */\n",
+       "body .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
+       "body .sc { color: #BA2121 } /* Literal.String.Char */\n",
+       "body .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
+       "body .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
+       "body .s2 { color: #BA2121 } /* Literal.String.Double */\n",
+       "body .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n",
+       "body .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
+       "body .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n",
+       "body .sx { color: #008000 } /* Literal.String.Other */\n",
+       "body .sr { color: #A45A77 } /* Literal.String.Regex */\n",
+       "body .s1 { color: #BA2121 } /* Literal.String.Single */\n",
+       "body .ss { color: #19177C } /* Literal.String.Symbol */\n",
+       "body .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
+       "body .fm { color: #0000FF } /* Name.Function.Magic */\n",
+       "body .vc { color: #19177C } /* Name.Variable.Class */\n",
+       "body .vg { color: #19177C } /* Name.Variable.Global */\n",
+       "body .vi { color: #19177C } /* Name.Variable.Instance */\n",
+       "body .vm { color: #19177C } /* Name.Variable.Magic */\n",
+       "body .il { color: #666666 } /* Literal.Number.Integer.Long */\n",
+       "\n",
+       "  \n",
+       "\n",
+       "\n",
+       "

\n", + "\n", + "
### BEGIN\n",
        "def _test_cell_():\n",
        "\t# %%testcell verbose\n",
        "\tb=3\n",
@@ -831,22 +1054,9 @@
        "\tdel _test_cell_\n",
        "_ # This will be added to global scope\n",
        "### END\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", - "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", - "\t\\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell verbose}\n", - "\t\\PY{n}{b}\\PY{o}{=}\\PY{l+m+mi}{3}\n", - "\t\\PY{k}{return} \\PY{n}{b} \\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell}\n", - "\\PY{k}{try}\\PY{p}{:}\n", - "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", - "\\PY{k}{finally}\\PY{p}{:}\n", - "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", - "\\PY{n}{\\PYZus{}} \\PY{c+c1}{\\PYZsh{} This will be added to global scope}\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", - "\\end{Verbatim}\n" + "
\n", + "\n", + "\n" ], "text/plain": [ "\n", @@ -901,80 +1111,104 @@ { "data": { "text/html": [ - "
### BEGIN\n",
+       "body .hll { background-color: #ffffcc }\n",
+       "body { background: #f8f8f8; }\n",
+       "body .c { color: #3D7B7B; font-style: italic } /* Comment */\n",
+       "body .err { border: 1px solid #FF0000 } /* Error */\n",
+       "body .k { color: #008000; font-weight: bold } /* Keyword */\n",
+       "body .o { color: #666666 } /* Operator */\n",
+       "body .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n",
+       "body .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n",
+       "body .cp { color: #9C6500 } /* Comment.Preproc */\n",
+       "body .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n",
+       "body .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n",
+       "body .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n",
+       "body .gd { color: #A00000 } /* Generic.Deleted */\n",
+       "body .ge { font-style: italic } /* Generic.Emph */\n",
+       "body .gr { color: #E40000 } /* Generic.Error */\n",
+       "body .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
+       "body .gi { color: #008400 } /* Generic.Inserted */\n",
+       "body .go { color: #717171 } /* Generic.Output */\n",
+       "body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
+       "body .gs { font-weight: bold } /* Generic.Strong */\n",
+       "body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
+       "body .gt { color: #0044DD } /* Generic.Traceback */\n",
+       "body .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
+       "body .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
+       "body .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
+       "body .kp { color: #008000 } /* Keyword.Pseudo */\n",
+       "body .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
+       "body .kt { color: #B00040 } /* Keyword.Type */\n",
+       "body .m { color: #666666 } /* Literal.Number */\n",
+       "body .s { color: #BA2121 } /* Literal.String */\n",
+       "body .na { color: #687822 } /* Name.Attribute */\n",
+       "body .nb { color: #008000 } /* Name.Builtin */\n",
+       "body .nc { color: #0000FF; font-weight: bold } /* Name.Class */\n",
+       "body .no { color: #880000 } /* Name.Constant */\n",
+       "body .nd { color: #AA22FF } /* Name.Decorator */\n",
+       "body .ni { color: #717171; font-weight: bold } /* Name.Entity */\n",
+       "body .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n",
+       "body .nf { color: #0000FF } /* Name.Function */\n",
+       "body .nl { color: #767600 } /* Name.Label */\n",
+       "body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */\n",
+       "body .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
+       "body .nv { color: #19177C } /* Name.Variable */\n",
+       "body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */\n",
+       "body .w { color: #bbbbbb } /* Text.Whitespace */\n",
+       "body .mb { color: #666666 } /* Literal.Number.Bin */\n",
+       "body .mf { color: #666666 } /* Literal.Number.Float */\n",
+       "body .mh { color: #666666 } /* Literal.Number.Hex */\n",
+       "body .mi { color: #666666 } /* Literal.Number.Integer */\n",
+       "body .mo { color: #666666 } /* Literal.Number.Oct */\n",
+       "body .sa { color: #BA2121 } /* Literal.String.Affix */\n",
+       "body .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
+       "body .sc { color: #BA2121 } /* Literal.String.Char */\n",
+       "body .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
+       "body .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
+       "body .s2 { color: #BA2121 } /* Literal.String.Double */\n",
+       "body .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n",
+       "body .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
+       "body .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n",
+       "body .sx { color: #008000 } /* Literal.String.Other */\n",
+       "body .sr { color: #A45A77 } /* Literal.String.Regex */\n",
+       "body .s1 { color: #BA2121 } /* Literal.String.Single */\n",
+       "body .ss { color: #19177C } /* Literal.String.Symbol */\n",
+       "body .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
+       "body .fm { color: #0000FF } /* Name.Function.Magic */\n",
+       "body .vc { color: #19177C } /* Name.Variable.Class */\n",
+       "body .vg { color: #19177C } /* Name.Variable.Global */\n",
+       "body .vi { color: #19177C } /* Name.Variable.Instance */\n",
+       "body .vm { color: #19177C } /* Name.Variable.Magic */\n",
+       "body .il { color: #666666 } /* Literal.Number.Integer.Long */\n",
+       "\n",
+       "  \n",
+       "\n",
+       "\n",
+       "

\n", + "\n", + "
### BEGIN\n",
        "def _test_cell_():\n",
        "\t# %%testcell dryrun\n",
        "\tb=1\n",
@@ -987,24 +1221,9 @@
        "\tdel _test_cell_\n",
        "if _ is not None: display(_)\n",
        "### END\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", - "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", - "\t\\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell dryrun}\n", - "\t\\PY{n}{b}\\PY{o}{=}\\PY{l+m+mi}{1}\n", - "\t\\PY{n}{b}\n", - "\t\n", - "\t\\PY{k}{assert} \\PY{k+kc}{False} \\PY{c+c1}{\\PYZsh{} we should not be here because code is supposed to not be executed}\n", - "\\PY{k}{try}\\PY{p}{:}\n", - "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", - "\\PY{k}{finally}\\PY{p}{:}\n", - "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", - "\\PY{k}{if} \\PY{n}{\\PYZus{}} \\PY{o+ow}{is} \\PY{o+ow}{not} \\PY{k+kc}{None}\\PY{p}{:} \\PY{n}{display}\\PY{p}{(}\\PY{n}{\\PYZus{}}\\PY{p}{)}\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", - "\\end{Verbatim}\n" + "
\n", + "\n", + "\n" ], "text/plain": [ "\n", @@ -1219,80 +1438,104 @@ { "data": { "text/html": [ - "
### BEGIN\n",
+       "body .hll { background-color: #ffffcc }\n",
+       "body { background: #f8f8f8; }\n",
+       "body .c { color: #3D7B7B; font-style: italic } /* Comment */\n",
+       "body .err { border: 1px solid #FF0000 } /* Error */\n",
+       "body .k { color: #008000; font-weight: bold } /* Keyword */\n",
+       "body .o { color: #666666 } /* Operator */\n",
+       "body .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n",
+       "body .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n",
+       "body .cp { color: #9C6500 } /* Comment.Preproc */\n",
+       "body .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n",
+       "body .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n",
+       "body .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n",
+       "body .gd { color: #A00000 } /* Generic.Deleted */\n",
+       "body .ge { font-style: italic } /* Generic.Emph */\n",
+       "body .gr { color: #E40000 } /* Generic.Error */\n",
+       "body .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
+       "body .gi { color: #008400 } /* Generic.Inserted */\n",
+       "body .go { color: #717171 } /* Generic.Output */\n",
+       "body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
+       "body .gs { font-weight: bold } /* Generic.Strong */\n",
+       "body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
+       "body .gt { color: #0044DD } /* Generic.Traceback */\n",
+       "body .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
+       "body .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
+       "body .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
+       "body .kp { color: #008000 } /* Keyword.Pseudo */\n",
+       "body .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
+       "body .kt { color: #B00040 } /* Keyword.Type */\n",
+       "body .m { color: #666666 } /* Literal.Number */\n",
+       "body .s { color: #BA2121 } /* Literal.String */\n",
+       "body .na { color: #687822 } /* Name.Attribute */\n",
+       "body .nb { color: #008000 } /* Name.Builtin */\n",
+       "body .nc { color: #0000FF; font-weight: bold } /* Name.Class */\n",
+       "body .no { color: #880000 } /* Name.Constant */\n",
+       "body .nd { color: #AA22FF } /* Name.Decorator */\n",
+       "body .ni { color: #717171; font-weight: bold } /* Name.Entity */\n",
+       "body .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n",
+       "body .nf { color: #0000FF } /* Name.Function */\n",
+       "body .nl { color: #767600 } /* Name.Label */\n",
+       "body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */\n",
+       "body .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
+       "body .nv { color: #19177C } /* Name.Variable */\n",
+       "body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */\n",
+       "body .w { color: #bbbbbb } /* Text.Whitespace */\n",
+       "body .mb { color: #666666 } /* Literal.Number.Bin */\n",
+       "body .mf { color: #666666 } /* Literal.Number.Float */\n",
+       "body .mh { color: #666666 } /* Literal.Number.Hex */\n",
+       "body .mi { color: #666666 } /* Literal.Number.Integer */\n",
+       "body .mo { color: #666666 } /* Literal.Number.Oct */\n",
+       "body .sa { color: #BA2121 } /* Literal.String.Affix */\n",
+       "body .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
+       "body .sc { color: #BA2121 } /* Literal.String.Char */\n",
+       "body .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
+       "body .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
+       "body .s2 { color: #BA2121 } /* Literal.String.Double */\n",
+       "body .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n",
+       "body .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
+       "body .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n",
+       "body .sx { color: #008000 } /* Literal.String.Other */\n",
+       "body .sr { color: #A45A77 } /* Literal.String.Regex */\n",
+       "body .s1 { color: #BA2121 } /* Literal.String.Single */\n",
+       "body .ss { color: #19177C } /* Literal.String.Symbol */\n",
+       "body .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
+       "body .fm { color: #0000FF } /* Name.Function.Magic */\n",
+       "body .vc { color: #19177C } /* Name.Variable.Class */\n",
+       "body .vg { color: #19177C } /* Name.Variable.Global */\n",
+       "body .vi { color: #19177C } /* Name.Variable.Instance */\n",
+       "body .vm { color: #19177C } /* Name.Variable.Magic */\n",
+       "body .il { color: #666666 } /* Literal.Number.Integer.Long */\n",
+       "\n",
+       "  \n",
+       "\n",
+       "\n",
+       "

\n", + "\n", + "
### BEGIN\n",
        "def _test_cell_():\n",
        "\t# %%testcell\n",
        "\treturn 'using %%testcell updates the last executed expression result "_"' # %%testcell\n",
@@ -1302,21 +1545,9 @@
        "\tdel _test_cell_\n",
        "_ # This will be added to global scope\n",
        "### END\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", - "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", - "\t\\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell}\n", - "\t\\PY{k}{return} \\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{using }\\PY{l+s+si}{\\PYZpc{}\\PYZpc{}}\\PY{l+s+s1}{testcell updates the last executed expression result }\\PY{l+s+s1}{\\PYZdq{}}\\PY{l+s+s1}{\\PYZus{}}\\PY{l+s+s1}{\\PYZdq{}}\\PY{l+s+s1}{\\PYZsq{}} \\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell}\n", - "\\PY{k}{try}\\PY{p}{:}\n", - "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", - "\\PY{k}{finally}\\PY{p}{:}\n", - "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", - "\\PY{n}{\\PYZus{}} \\PY{c+c1}{\\PYZsh{} This will be added to global scope}\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", - "\\end{Verbatim}\n" + "
\n", + "\n", + "\n" ], "text/plain": [ "\n", @@ -1376,80 +1607,104 @@ { "data": { "text/html": [ - "
### BEGIN\n",
+       "body .hll { background-color: #ffffcc }\n",
+       "body { background: #f8f8f8; }\n",
+       "body .c { color: #3D7B7B; font-style: italic } /* Comment */\n",
+       "body .err { border: 1px solid #FF0000 } /* Error */\n",
+       "body .k { color: #008000; font-weight: bold } /* Keyword */\n",
+       "body .o { color: #666666 } /* Operator */\n",
+       "body .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n",
+       "body .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n",
+       "body .cp { color: #9C6500 } /* Comment.Preproc */\n",
+       "body .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n",
+       "body .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n",
+       "body .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n",
+       "body .gd { color: #A00000 } /* Generic.Deleted */\n",
+       "body .ge { font-style: italic } /* Generic.Emph */\n",
+       "body .gr { color: #E40000 } /* Generic.Error */\n",
+       "body .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
+       "body .gi { color: #008400 } /* Generic.Inserted */\n",
+       "body .go { color: #717171 } /* Generic.Output */\n",
+       "body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
+       "body .gs { font-weight: bold } /* Generic.Strong */\n",
+       "body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
+       "body .gt { color: #0044DD } /* Generic.Traceback */\n",
+       "body .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
+       "body .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
+       "body .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
+       "body .kp { color: #008000 } /* Keyword.Pseudo */\n",
+       "body .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
+       "body .kt { color: #B00040 } /* Keyword.Type */\n",
+       "body .m { color: #666666 } /* Literal.Number */\n",
+       "body .s { color: #BA2121 } /* Literal.String */\n",
+       "body .na { color: #687822 } /* Name.Attribute */\n",
+       "body .nb { color: #008000 } /* Name.Builtin */\n",
+       "body .nc { color: #0000FF; font-weight: bold } /* Name.Class */\n",
+       "body .no { color: #880000 } /* Name.Constant */\n",
+       "body .nd { color: #AA22FF } /* Name.Decorator */\n",
+       "body .ni { color: #717171; font-weight: bold } /* Name.Entity */\n",
+       "body .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n",
+       "body .nf { color: #0000FF } /* Name.Function */\n",
+       "body .nl { color: #767600 } /* Name.Label */\n",
+       "body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */\n",
+       "body .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
+       "body .nv { color: #19177C } /* Name.Variable */\n",
+       "body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */\n",
+       "body .w { color: #bbbbbb } /* Text.Whitespace */\n",
+       "body .mb { color: #666666 } /* Literal.Number.Bin */\n",
+       "body .mf { color: #666666 } /* Literal.Number.Float */\n",
+       "body .mh { color: #666666 } /* Literal.Number.Hex */\n",
+       "body .mi { color: #666666 } /* Literal.Number.Integer */\n",
+       "body .mo { color: #666666 } /* Literal.Number.Oct */\n",
+       "body .sa { color: #BA2121 } /* Literal.String.Affix */\n",
+       "body .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
+       "body .sc { color: #BA2121 } /* Literal.String.Char */\n",
+       "body .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
+       "body .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
+       "body .s2 { color: #BA2121 } /* Literal.String.Double */\n",
+       "body .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n",
+       "body .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
+       "body .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n",
+       "body .sx { color: #008000 } /* Literal.String.Other */\n",
+       "body .sr { color: #A45A77 } /* Literal.String.Regex */\n",
+       "body .s1 { color: #BA2121 } /* Literal.String.Single */\n",
+       "body .ss { color: #19177C } /* Literal.String.Symbol */\n",
+       "body .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
+       "body .fm { color: #0000FF } /* Name.Function.Magic */\n",
+       "body .vc { color: #19177C } /* Name.Variable.Class */\n",
+       "body .vg { color: #19177C } /* Name.Variable.Global */\n",
+       "body .vi { color: #19177C } /* Name.Variable.Instance */\n",
+       "body .vm { color: #19177C } /* Name.Variable.Magic */\n",
+       "body .il { color: #666666 } /* Literal.Number.Integer.Long */\n",
+       "\n",
+       "  \n",
+       "\n",
+       "\n",
+       "

\n", + "\n", + "
### BEGIN\n",
        "def _test_cell_():\n",
        "\t# %%testcelln\n",
        "\treturn '%%testcelln does not change "_"' # %%testcell\n",
@@ -1459,21 +1714,9 @@
        "\tdel _test_cell_\n",
        "if _ is not None: display(_)\n",
        "### END\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", - "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", - "\t\\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcelln}\n", - "\t\\PY{k}{return} \\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+si}{\\PYZpc{}\\PYZpc{}}\\PY{l+s+s1}{testcelln does not change }\\PY{l+s+s1}{\\PYZdq{}}\\PY{l+s+s1}{\\PYZus{}}\\PY{l+s+s1}{\\PYZdq{}}\\PY{l+s+s1}{\\PYZsq{}} \\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell}\n", - "\\PY{k}{try}\\PY{p}{:}\n", - "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", - "\\PY{k}{finally}\\PY{p}{:}\n", - "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", - "\\PY{k}{if} \\PY{n}{\\PYZus{}} \\PY{o+ow}{is} \\PY{o+ow}{not} \\PY{k+kc}{None}\\PY{p}{:} \\PY{n}{display}\\PY{p}{(}\\PY{n}{\\PYZus{}}\\PY{p}{)}\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", - "\\end{Verbatim}\n" + "
\n", + "\n", + "\n" ], "text/plain": [ "\n", From 6f57f59789451f5867c12104be3d95c465dbcbc3 Mon Sep 17 00:00:00 2001 From: Stefano Giomo Date: Tue, 4 Nov 2025 08:13:49 +0000 Subject: [PATCH 02/18] Initial support for inout syntax (input)->(output) --- README.md | 809 ++++++++++++++++++++++++ demo/testcell_demo.ipynb | 85 +++ nbs/01a_arguments.ipynb | 360 +++++++++++ nbs/02_testcell.ipynb | 607 +++++++++++++++++- nbs/index.ipynb | 1281 +++++++++++++++++++++++++------------- testcell/__init__.py | 85 ++- testcell/_modidx.py | 10 +- testcell/inout.py | 61 ++ 8 files changed, 2841 insertions(+), 457 deletions(-) create mode 100644 nbs/01a_arguments.ipynb create mode 100644 testcell/inout.py diff --git a/README.md b/README.md index ba10a91..3d17e38 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,116 @@ a = "'a' is not polluting global scope" a ``` + + + + + + + + + +

+
### BEGIN
+def _test_cell_():
+    #| echo: false
+    a = "'a' is not polluting global scope"
+    return a # %%testcell
+try:
+    _ = _test_cell_()
+finally:
+    del _test_cell_
+_ # This will be added to global scope
+### END
+
+ + + "'a' is not polluting global scope" If you’re just interested in seeing what will be executed, but actually @@ -70,6 +180,116 @@ a = "'a' is not polluting global scope" a ``` + + + + + + + + + +

+
### BEGIN
+def _test_cell_():
+    #| echo: false
+    a = "'a' is not polluting global scope"
+    return a # %%testcell
+try:
+    _ = _test_cell_()
+finally:
+    del _test_cell_
+if _ is not None: display(_)
+### END
+
+ + + If you add a semicolon `;` at the end of your last statement no `return` statement is added and nothing is displayed like a normal jupyter cell. @@ -79,6 +299,116 @@ a = "'a' is not polluting global scope" a; ``` + + + + + + + + + +

+
### BEGIN
+def _test_cell_():
+    #| echo: false
+    a = "'a' is not polluting global scope"
+    a;
+try:
+    _ = _test_cell_()
+finally:
+    del _test_cell_
+_ # This will be added to global scope
+### END
+
+ + + `testcell` works seamlessly with existing `print` or `display`statements on last line: @@ -88,6 +418,116 @@ a = "'a' is not polluting global scope" print(a) ``` + + + + + + + + + +

+
### BEGIN
+def _test_cell_():
+    #| echo: false
+    a = "'a' is not polluting global scope"
+    return print(a) # %%testcell
+try:
+    _ = _test_cell_()
+finally:
+    del _test_cell_
+_ # This will be added to global scope
+### END
+
+ + + 'a' is not polluting global scope Moreover, thanks to `ast`, it properly deals with complex situations @@ -101,6 +541,117 @@ a = "'a' is not polluting global scope" # this is a comment on last line ``` + + + + + + + + + +

+
### BEGIN
+def _test_cell_():
+    #| echo: false
+    a = "'a' is not polluting global scope"
+    return (a,
+     True) # %%testcell
+try:
+    _ = _test_cell_()
+finally:
+    del _test_cell_
+_ # This will be added to global scope
+### END
+
+ + + ("'a' is not polluting global scope", True) ### Skip execution @@ -110,6 +661,11 @@ usueful when you want to keep around the code but don’t actually run it. It’s also possible to skip **all cells markked with `%%testcell`** using the following syntax: `testcell.global_skip=True`. +``` python +%%testcell skip +raise ValueError('This should not be executed') +``` +
This cell has been skipped
@@ -200,6 +756,255 @@ aaa 'global variable' +There is a dedicated syntax to enable passing variables to an isolated +context and saving variables of objects back to the main context tith +the `(inputs)->(outputs)` syntax + +``` python +%%testcelln (aaa) +# Input only +aaa # global variable +``` + + 'global variable' + +``` python +%%testcelln (aaa)->(bbb,ccc) debug +# Input->Output and debug option + +print(aaa) # global variable +bbb=123 +def ccc(): return 567 +``` + + + + + + + + + + +

+
### BEGIN
+def _test_cell_():
+    global bbb
+    global ccc
+    #| echo: false
+    # Input->Output and debug option
+    
    print(aaa) # global variable
+    bbb=123
+    def ccc(): return 567
+try:
+    _ = _test_cell_()
+finally:
+    del _test_cell_
+if _ is not None: display(_)
+### END
+
+ + + + global variable + + + + + + + + + + +

+
### GLOBALS UPDATE CODE:
+global bbb; bbb=locals()["bbb"]
+global ccc; ccc=locals()["ccc"]
+###
+
+ + + +``` python +# Variable bbb has been created in this context +assert bbb==123 +assert ccc()==567 + +# cleaup +del bbb,ccc +``` + ``` python %%testcell # WARNING: this will alter the state of global variable: @@ -221,6 +1026,10 @@ del aaa - PROJECT PAGE: - DOCUMENTATION: - PYPI: +- DETAILED DEMO: + \[https://github.com/artste/testcell/blob/main/demo/testcell_demo.ipynb\] +- LAUNCHING BLOG: + \[https://artste.github.io/blog/posts/introducing-testcell\] - COLAB DEMO: [testcell_demo.ipynb](https://colab.research.google.com/github/artste/testcell/blob/main/demo/testcell_demo.ipynb) - KAGGLE SAMPLE NOTEBOOK: diff --git a/demo/testcell_demo.ipynb b/demo/testcell_demo.ipynb index d34700b..eb5a73d 100644 --- a/demo/testcell_demo.ipynb +++ b/demo/testcell_demo.ipynb @@ -357,6 +357,91 @@ "'This is a banner for %%testcell noglobals cell'" ] }, + { + "cell_type": "markdown", + "id": "451f0524-3e5e-4881-85b2-bdd7ea48c2a3", + "metadata": {}, + "source": [ + "### `%%testcelln` (sample_variable)->(new_func)\n", + "This is the most advanced use case and let you execute code on an isolated context, but adding access to `sample_variable` and returning to the global context the function `new_func`.\n", + "Additionaly we're using the \"debug\" option that shows step by step what's happening under the hood." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24c7c686-7a9a-458c-b486-ba353c3c4b52", + "metadata": {}, + "outputs": [], + "source": [ + "%%testcelln (sample_variable)->(new_func) debug\n", + "def new_func(): return sample_variable\n", + "\n", + "new_func()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12387ed0-89c9-45dd-9d2f-ab7c0673ceef", + "metadata": {}, + "outputs": [], + "source": [ + "assert new_func()==sample_variable\n", + "del new_func #cleanup state" + ] + }, + { + "cell_type": "markdown", + "id": "08394121-0f6e-4b73-b9a6-4e6a43dc4859", + "metadata": {}, + "source": [ + "This is an exmaple of **input only**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "634d2155-545b-4aae-957e-11c3491d5e6d", + "metadata": {}, + "outputs": [], + "source": [ + "%%testcelln (sample_variable)\n", + "def new_func(): return sample_variable\n", + "new_func()" + ] + }, + { + "cell_type": "markdown", + "id": "581c1dc9-db12-42b6-a6ca-233cda556e1b", + "metadata": {}, + "source": [ + "This is an exmaple of **output only**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fb8ba9cb-6082-4071-89e5-9d8dbdf118fb", + "metadata": {}, + "outputs": [], + "source": [ + "%%testcelln ->(new_func)\n", + "def new_func(): return 123\n", + "new_func()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ddc07336-e208-440f-969d-b88468a3da48", + "metadata": {}, + "outputs": [], + "source": [ + "assert new_func()==123\n", + "del new_func #cleanup state" + ] + }, { "cell_type": "markdown", "id": "05290ea3", diff --git a/nbs/01a_arguments.ipynb b/nbs/01a_arguments.ipynb new file mode 100644 index 0000000..63e3072 --- /dev/null +++ b/nbs/01a_arguments.ipynb @@ -0,0 +1,360 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "40730acc", + "metadata": {}, + "source": [ + "# arguments" + ] + }, + { + "cell_type": "markdown", + "id": "1e4407a1", + "metadata": {}, + "source": [ + "This module is meant to add all the support functions for *arguments* and *inout* parsing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c927be30", + "metadata": {}, + "outputs": [], + "source": [ + "#| default_exp inout" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ea55fe32", + "metadata": {}, + "outputs": [], + "source": [ + "from fastcore.test import *" + ] + }, + { + "cell_type": "markdown", + "id": "81f5dc52", + "metadata": {}, + "source": [ + "## Find support function" + ] + }, + { + "cell_type": "markdown", + "id": "1e602995", + "metadata": {}, + "source": [ + "`optional_find` let you search a string in forward or reverse order using one or more *templates* (aka: reference sub-strings).\n", + "\n", + "It returns the *first* occurrence that is the leftmost in forward direction and rightmost in reverse direction.\n", + "The occurrence returned is a tuple `(position,template)` where:\n", + "+ `position`: is the position inside the string.\n", + "+ `template`: is the copy of the template found. We need this to understand where that string finishes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "95d0ba51", + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "def optional_find(x,cc,reverse=False):\n", + " if isinstance(cc,str): cc = [cc] # listify\n", + " t = [(x.rfind(c) if reverse else x.find(c),c) for c in cc]\n", + " t = [(f,c) for f,c in t if f != -1]\n", + " if len(t)==0: return None\n", + " return max(t) if reverse else min(t)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "efb977d3", + "metadata": {}, + "outputs": [], + "source": [ + "# Single\n", + "test_eq( optional_find('abc','a')[0] , 0)\n", + "test_eq( optional_find('abc','c')[0] , 2)\n", + "test_eq( optional_find('abc','bc')[0] , 1)\n", + "test_eq( optional_find('abc','z'), None)\n", + "test_eq( optional_find('abcdefghiab','a',reverse=True)[0] , 9)\n", + "test_eq( optional_find('abc','bc',reverse=True)[0] , 1)\n", + "\n", + "# Multi\n", + "test_eq( optional_find('abcdefghiab',['aa','fg','ia'],reverse=False)[0] , 5)\n", + "test_eq( optional_find('abcdefghiab',['aa','fg','ia'],reverse=True)[0] , 8)\n", + "test_eq( optional_find('abcdefghiab',['aa','ia','fg'],reverse=True)[0] , 8)" + ] + }, + { + "cell_type": "markdown", + "id": "b547c7cb", + "metadata": {}, + "source": [ + "## Character level utility" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8a6e5b12", + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "def count_char(x,c):\n", + " # Count how many times c appears in x\n", + " return sum(map(lambda y: y==c,x))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6954a2d", + "metadata": {}, + "outputs": [], + "source": [ + "test_eq( count_char('abacda','a') , 3 )\n", + "test_eq( count_char('abacda','b') , 1 )\n", + "test_eq( count_char('abacda','z') , 0 )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e5f25890", + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "def count_delta(x,a='(',b=')'):\n", + " # Return the difference between the count of \"a\" and \"b\".\n", + " return count_char(x,a) - count_char(x,b)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd0e9679", + "metadata": {}, + "outputs": [], + "source": [ + "test_eq(count_delta('asd(asd)'),0)\n", + "test_eq(count_delta('asd(asd'),1)\n", + "test_eq(count_delta('asd(a(sd'),2)\n", + "test_eq(count_delta('asd(a(sd)))'),-1)" + ] + }, + { + "cell_type": "markdown", + "id": "d77e6418", + "metadata": {}, + "source": [ + "## String split utility\n", + "\n", + "This is just a bit of syntactic sugar" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49ec1d10", + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "def split_and_strip(x,splitter):\n", + " t = [t.strip() for t in x.split(splitter)]\n", + " if t==['']: return []\n", + " return t" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ad718a5c", + "metadata": {}, + "outputs": [], + "source": [ + "test_eq(split_and_strip('a,b,c',','),['a','b','c'])\n", + "test_eq(split_and_strip(' a, b,c ',','),['a','b','c'])\n", + "test_eq(split_and_strip('',','),[])" + ] + }, + { + "cell_type": "markdown", + "id": "704acae9", + "metadata": {}, + "source": [ + "## Inout util\n", + "\n", + "`inout` is the *name* of the portion of the complete arguments string that refers to input and output parameters.\n", + "\n", + "For example in this magick line: `%%testcell noglobals (aaa,bbb) ->(ccc)` \n", + "+ *raw_arguments*: `noglobals `\n", + "+ *inout*: `(aaa,bbb) ->(ccc)`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c2463950", + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "def process_inout(x,splitter='->'):\n", + " if x is None: return None\n", + " t = split_and_strip(x,splitter)\n", + " for v,c,n in zip(t,map(count_delta,t),map(lambda s: count_char(s,'('),t)): \n", + " if n>1: raise ValueError(f'Too much parenthesis on \"{v}\"')\n", + " if c>0: raise ValueError(f'Missing closing parenthesis on \"{v}\"')\n", + " t = [x[1:-1] for x in t]\n", + " if len(t)==0: raise ValueError('No groups available')\n", + " if len(t)>2: raise ValueError(f'You shouold have only one \"{splitter}\" symbol')\n", + " if len(t)==1: return split_and_strip(t[0],','),[]\n", + " if len(t)==2: return split_and_strip(t[0],','),split_and_strip(t[1],',')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "277e15f2", + "metadata": {}, + "outputs": [], + "source": [ + "test_eq(process_inout(None,splitter='->'),None)\n", + "\n", + "test_eq(process_inout('(a,b,cc)->(d,ee)',splitter='->'),(['a','b','cc'],['d','ee']))\n", + "test_eq(process_inout(' (a,b,cc) -> (d,ee) ',splitter='->'),(['a','b','cc'],['d','ee']))\n", + "test_eq(process_inout(' (a,b,cc) -> () ',splitter='->'),(['a','b','cc'],[]))\n", + "test_eq(process_inout(' (a,b,cc) ',splitter='->'),(['a','b','cc'],[]))\n", + "test_eq(process_inout('()->(a,b,cc) ',splitter='->'),([],['a','b','cc']))\n", + "test_eq(process_inout('->(a,b,cc) ',splitter='->'),([],['a','b','cc']))\n", + "\n", + "test_fail(lambda: process_inout('(a,b,cc)(d,ee)'), contains='Too much parenthesis')\n", + "test_fail(lambda: process_inout('(a,b,cc) (d,ee)'), contains='Too much parenthesis')\n", + "test_fail(lambda: process_inout('(a,b,cc) (->d,ee)'), contains='Too much parenthesis')\n", + "test_fail(lambda: process_inout('(a,b,cc->)(d,ee)'), contains='Missing closing parenthesis')\n", + "test_fail(lambda: process_inout('(a,b,cc) - > (d,ee)'), contains='Too much parenthesis')\n", + "test_fail(lambda: process_inout('(a,b,cc) ? (d,ee)'), contains='Too much parenthesis')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22f7d40b", + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "def separate_args_and_inout(x):\n", + " if x is None: return None\n", + " if (start_t := optional_find(x,['(','->'],reverse=False)) and (end_t := optional_find(x,[')','->'],reverse=True)):\n", + " start,_ = start_t\n", + " end, c = end_t\n", + " length = len(c)\n", + " return x[:start]+x[end+length:],x[start:end+length]\n", + " return x,None" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "228f1202", + "metadata": {}, + "outputs": [], + "source": [ + "test_eq(separate_args_and_inout(None),None)\n", + "\n", + "test_eq(separate_args_and_inout(''),['',None])\n", + "test_eq(separate_args_and_inout('verbose'),['verbose',None])\n", + "test_eq(separate_args_and_inout('dryrun verbose'),['dryrun verbose',None])\n", + "test_eq(separate_args_and_inout('(a,b)->(c,d)'),['','(a,b)->(c,d)'])\n", + "test_eq(separate_args_and_inout('(a,b)'),['','(a,b)'])\n", + "test_eq(separate_args_and_inout('->(c,d)'),['','->(c,d)'])\n", + "\n", + "test_eq(separate_args_and_inout('dryrun verbose (a,b)'),['dryrun verbose ','(a,b)'])\n", + "test_eq(separate_args_and_inout('dryrun verbose (a,b) -> (c,d) '),['dryrun verbose ','(a,b) -> (c,d)'])\n", + "test_eq(separate_args_and_inout('dryrun verbose () -> (c,d) '),['dryrun verbose ','() -> (c,d)'])\n", + "test_eq(separate_args_and_inout('dryrun verbose (a,b) -> ()'),['dryrun verbose ','(a,b) -> ()'])\n", + "test_eq(separate_args_and_inout('dryrun verbose (a,b) '),['dryrun verbose ','(a,b)'])\n", + "\n", + "test_eq(separate_args_and_inout('dryrun (a,b) verbose '),['dryrun verbose ','(a,b)'])\n", + "test_eq(separate_args_and_inout('dryrun (a,b) -> (c,d) verbose'),['dryrun verbose','(a,b) -> (c,d)'])\n", + "test_eq(separate_args_and_inout('dryrun () -> (c,d) verbose'),['dryrun verbose','() -> (c,d)'])\n", + "test_eq(separate_args_and_inout('dryrun () -> (c,d)verbose'),['dryrun verbose','() -> (c,d)'])\n", + "test_eq(separate_args_and_inout('dryrun() -> (c,d) verbose'),['dryrun verbose','() -> (c,d)'])\n", + "test_eq(separate_args_and_inout('dryrun (a,b)->() verbose'),['dryrun verbose','(a,b)->()'])\n", + "test_eq(separate_args_and_inout('dryrun (a, b) verbose'),['dryrun verbose','(a, b)'])\n", + "\n", + "test_eq(separate_args_and_inout('dryrun -> (c,d)verbose'),['dryrun verbose','-> (c,d)'])\n", + "test_eq(separate_args_and_inout('dryrun (c,d)->verbose'),['dryrun verbose','(c,d)->'])" + ] + }, + { + "cell_type": "markdown", + "id": "104897cf", + "metadata": {}, + "source": [ + "## Consume inout" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2e72903f", + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "def validate_and_update_inputs(inputs:list,state:dict,)->dict:\n", + " s = set(inputs)\n", + " ret = {}\n", + " for k in inputs:\n", + " if k not in state: raise ValueError(f'Unable to find object \"{k}\" in current state')\n", + " ret[k] = state[k]\n", + " return ret" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c95e8718", + "metadata": {}, + "outputs": [], + "source": [ + "test_eq( validate_and_update_inputs('a',{'a':1, 'b':2}) , {'a':1} )\n", + "test_fail(lambda: validate_and_update_inputs('a',{'b':1}) , contains='Unable' )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba812f8b", + "metadata": {}, + "outputs": [], + "source": [ + "#| hide\n", + "import nbdev; nbdev.nbdev_export()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "python3", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/nbs/02_testcell.ipynb b/nbs/02_testcell.ipynb index 76e3907..1a20a97 100644 --- a/nbs/02_testcell.ipynb +++ b/nbs/02_testcell.ipynb @@ -59,7 +59,8 @@ "outputs": [], "source": [ "#| export\n", - "from testcell.core import auto_return" + "from testcell.core import auto_return\n", + "from testcell.inout import separate_args_and_inout, process_inout, split_and_strip, validate_and_update_inputs" ] }, { @@ -400,6 +401,102 @@ "''').__repr__())" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Valid arguments" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from fastcore.test import *" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export \n", + "testcell_valid_args = {'verbose','dryrun','noglobals','noreturn','skip','banner','debug'} # full commands set" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export \n", + "def parse_args(x):\n", + " t = set([c for c in split_and_strip(x,' ') if c != ''])\n", + " diff = t.difference(testcell_valid_args)\n", + " if len(diff)>0: raise ValueError(f'Invalid arguments passed: \"{\",\".join(diff)}\"')\n", + " return t" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_eq(parse_args(''),set())\n", + "test_eq(parse_args('verbose dryrun'),{'dryrun', 'verbose'})\n", + "test_eq(parse_args('verbose dryrun '),{'dryrun', 'verbose'})\n", + "test_fail(lambda:parse_args('verbose dryrun xxx yyy'),contains='Invalid arguments passed')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Parse testcell arguments string" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "from collections import namedtuple\n", + "TestcellArgs = namedtuple('TestcellArgs','args inout')\n", + "Inout = namedtuple('Inout','input output')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "def parse_testcell_args(x:str)->TestcellArgs:\n", + " raw_args,raw_inout = separate_args_and_inout(x)\n", + " return TestcellArgs(parse_args(raw_args),None if raw_inout is None else Inout(*process_inout(raw_inout)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_eq(parse_testcell_args('dryrun verbose '),TestcellArgs({'dryrun','verbose'},None))\n", + "test_eq(parse_testcell_args('dryrun verbose (a,b)'),TestcellArgs({'dryrun','verbose'},Inout(['a','b'],[])))\n", + "test_eq(parse_testcell_args('dryrun verbose (a,b) ->(c)'),TestcellArgs({'dryrun','verbose'},Inout(['a','b'],['c'])))\n", + "\n", + "test_fail(lambda:parse_testcell_args('dryrun verbose (a,b) ->c'), contains='Invalid arguments passed')" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -417,6 +514,8 @@ "@register_cell_magic\n", "@needs_local_scope\n", "def testcell(line, cell, local_ns):\n", + " args,inout = parse_testcell_args(line)\n", + " \n", " # Parse arguments\n", " verbose = 'verbose' in line # enable verbose \n", " dryrun = 'dryrun' in line # this will avoid running the code and just print out the code like verbose\n", @@ -424,23 +523,37 @@ " noreturn = 'noreturn' in line # display but does not return anything, so no memory \"footprint\" after execution\n", " skip = ('skip' in line) or global_skip # skip cell execution\n", " use_banner = ('banner' in line) or global_use_banner # shows a contextual banner at top of the cell\n", + " debug = 'debug' in args # debug mode shows both code and all the update to the globals \n", " \n", " # arguments rules\n", " if noglobals or dryrun: noreturn=True\n", " if dryrun: verbose=True\n", + " if debug: verbose=True\n", "\n", " # skip\n", " if skip: \n", " display(skip_message_box)\n", " return None # exits\n", - "\n", " \n", " # Do the job\n", " cell = auto_return(cell)\n", " lines = cell.splitlines()\n", + " \n", + " # Globals management\n", + " _globals = {} if noglobals else local_ns\n", + " _locals = {'_':None} if noreturn else None # we mask '_' with a local variable to prevent it affecting global scope\n", "\n", + " # Deal with inout\n", + " outputs = None\n", + " globals_definitions = []\n", + " if inout is not None:\n", + " inputs,outputs = inout.input,inout.output\n", + " _globals.update(validate_and_update_inputs(inputs,local_ns)) # inputs\n", + " for o in outputs: globals_definitions += ['\\t'+f'global {o}'] # outputs\n", + " \n", " # Wrap inside a function and execute it\n", " arr = ['def _test_cell_():']\n", + " arr += globals_definitions\n", " arr += ['\\t'+x for x in lines]\n", " arr += ['try:\\n\\t_ = _test_cell_()'] # execute it and assign result to '_'\n", " arr += ['finally:\\n\\tdel _test_cell_'] # delete it\n", @@ -450,14 +563,18 @@ " arr += ['_ # This will be added to global scope'] # having this as last line makes the same behavior as normal cell\n", " wrapped_cell = '\\n'.join(arr)\n", "\n", + " if use_banner: display(noglobals_message_box if noglobals else testcell_message_box)\n", " if verbose: display(Code('\\n### BEGIN\\n'+wrapped_cell+'\\n### END',language='python'))\n", "\n", - " _globals = {} if noglobals else local_ns\n", - " _locals = {'_':None} if noreturn else None # we mask '_' with a local variable to prevent it affecting global scope\n", - " if not 'dryrun' in line: \n", - " if use_banner: display(noglobals_message_box if noglobals else testcell_message_box)\n", - " exec(wrapped_cell,_globals,_locals)\n", - " \n", + " if not dryrun: exec(wrapped_cell,_globals,_locals)\n", + " \n", + " if (outputs is not None) and (len(outputs)>0) and (not dryrun):\n", + " arr2 = []\n", + " for o in outputs: arr2 += [f'global {o}; {o}=locals()[\"{o}\"]'] # this forwards objects to global scope\n", + " globals_update_code = '\\n'.join(arr2)\n", + " if debug: display(Code('\\n### GLOBALS UPDATE CODE:\\n'+globals_update_code+'\\n###',language='python'))\n", + " exec(globals_update_code,local_ns,_globals)\n", + " \n", " return None if noreturn else _globals.get('_',None) # this closes the loop of integration" ] }, @@ -1332,6 +1449,47 @@ "Markdown('''This shows a contextual banner, use: `testcell.global_use_banner=True` to always show the banner''')" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " testcell noglobals\n", + "
\n", + " " + ], + "text/plain": [ + "🟢 testcell noglobals" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/markdown": [ + "This is a banner for noglobals" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln banner\n", + "# %%testcelln banner\n", + "from IPython.display import Markdown\n", + "Markdown('''This is a banner for noglobals''')" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1537,7 +1695,7 @@ "\n", "
### BEGIN\n",
        "def _test_cell_():\n",
-       "\t# %%testcell\n",
+       "\t# %%testcell verbose\n",
        "\treturn 'using %%testcell updates the last executed expression result "_"' # %%testcell\n",
        "try:\n",
        "\t_ = _test_cell_()\n",
@@ -1553,7 +1711,7 @@
        "\n",
        "### BEGIN\n",
        "def _test_cell_():\n",
-       "\t# %%testcell\n",
+       "\t# %%testcell verbose\n",
        "\treturn 'using %%testcell updates the last executed expression result \"_\"' # %%testcell\n",
        "try:\n",
        "\t_ = _test_cell_()\n",
@@ -1579,7 +1737,7 @@
    ],
    "source": [
     "%%testcell verbose\n",
-    "# %%testcell\n",
+    "# %%testcell verbose\n",
     "'using %%testcell updates the last executed expression result \"_\"'"
    ]
   },
@@ -1767,6 +1925,433 @@
     "`%%testcelln` instead includes the `noreturn` option that avoids modifying last command execution `_`."
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## inout syntax\n",
+    "\n",
+    "This is the most advanced option and is meant to enable selectively passing data in and out the context of testecell.\n",
+    "It's based on the **cell as function idea**, with the ability not only to input and output data but also symbols."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Define some stuff in the global space\n",
+    "def my_global_func(x): return x*2\n",
+    "my_global_variable = 123"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Selectively add objects from global state\n",
+    "\n",
+    "This is useful when we're developing something in isolation, but we need access to libraries, constants or other objects from the main scope."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "dict_keys(['my_global_func', 'my_global_variable', '__builtins__'])\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%testcelln (my_global_func,my_global_variable)\n",
+    "print(globals().keys())\n",
+    "\n",
+    "# We can now access only to these two additional functions\n",
+    "assert my_global_variable==123\n",
+    "assert my_global_func(1)==2"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Selectively pushing changes to global state \n",
+    "In this case we're "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assert 'kkk' not in globals().keys()\n",
+    "assert 'fff' not in globals().keys()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "\n",
+       "\n",
+       "\n",
+       "\n",
+       "  \n",
+       "  \n",
+       "  \n",
+       "\n",
+       "\n",
+       "

\n", + "\n", + "
### BEGIN\n",
+       "def _test_cell_():\n",
+       "\tglobal kkk\n",
+       "\tglobal fff\n",
+       "\t\n",
+       "\tkkk = 'this is a global variable created inside testcelln cell'\n",
+       "\tdef fff(): \n",
+       "\t    # this is a global function created inside testcelln cell\n",
+       "\t    return my_global_variable # we use a global variable that should not be accessible\n",
+       "\t\n",
+       "\treturn fff() # %%testcell\n",
+       "try:\n",
+       "\t_ = _test_cell_()\n",
+       "finally:\n",
+       "\tdel _test_cell_\n",
+       "if _ is not None: display(_)\n",
+       "### END\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "\n", + "### BEGIN\n", + "def _test_cell_():\n", + "\tglobal kkk\n", + "\tglobal fff\n", + "\t\n", + "\tkkk = 'this is a global variable created inside testcelln cell'\n", + "\tdef fff(): \n", + "\t # this is a global function created inside testcelln cell\n", + "\t return my_global_variable # we use a global variable that should not be accessible\n", + "\t\n", + "\treturn fff() # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "if _ is not None: display(_)\n", + "### END" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "123" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
### GLOBALS UPDATE CODE:\n",
+       "global kkk; kkk=locals()["kkk"]\n",
+       "global fff; fff=locals()["fff"]\n",
+       "###\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "\n", + "### GLOBALS UPDATE CODE:\n", + "global kkk; kkk=locals()[\"kkk\"]\n", + "global fff; fff=locals()[\"fff\"]\n", + "###" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln (my_global_variable)->(kkk,fff) debug\n", + "\n", + "kkk = 'this is a global variable created inside testcelln cell'\n", + "def fff(): \n", + " # this is a global function created inside testcelln cell\n", + " return my_global_variable # we use a global variable that should not be accessible\n", + "\n", + "fff()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert kkk=='this is a global variable created inside testcelln cell'\n", + "assert fff()==123" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# cleanup\n", + "del kkk\n", + "del fff" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This last example is about only returning data:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert 'fff' not in globals().keys()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%testcelln ->(fff)\n", + "def a(): return 7\n", + "def fff(): return a()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_eq(fff(),7)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# cleanup\n", + "del fff" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/nbs/index.ipynb b/nbs/index.ipynb index 58c9e1d..af7ffe8 100644 --- a/nbs/index.ipynb +++ b/nbs/index.ipynb @@ -135,80 +135,104 @@ { "data": { "text/html": [ - "
### BEGIN\n",
+       "body .hll { background-color: #ffffcc }\n",
+       "body { background: #f8f8f8; }\n",
+       "body .c { color: #3D7B7B; font-style: italic } /* Comment */\n",
+       "body .err { border: 1px solid #FF0000 } /* Error */\n",
+       "body .k { color: #008000; font-weight: bold } /* Keyword */\n",
+       "body .o { color: #666666 } /* Operator */\n",
+       "body .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n",
+       "body .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n",
+       "body .cp { color: #9C6500 } /* Comment.Preproc */\n",
+       "body .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n",
+       "body .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n",
+       "body .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n",
+       "body .gd { color: #A00000 } /* Generic.Deleted */\n",
+       "body .ge { font-style: italic } /* Generic.Emph */\n",
+       "body .gr { color: #E40000 } /* Generic.Error */\n",
+       "body .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
+       "body .gi { color: #008400 } /* Generic.Inserted */\n",
+       "body .go { color: #717171 } /* Generic.Output */\n",
+       "body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
+       "body .gs { font-weight: bold } /* Generic.Strong */\n",
+       "body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
+       "body .gt { color: #0044DD } /* Generic.Traceback */\n",
+       "body .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
+       "body .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
+       "body .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
+       "body .kp { color: #008000 } /* Keyword.Pseudo */\n",
+       "body .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
+       "body .kt { color: #B00040 } /* Keyword.Type */\n",
+       "body .m { color: #666666 } /* Literal.Number */\n",
+       "body .s { color: #BA2121 } /* Literal.String */\n",
+       "body .na { color: #687822 } /* Name.Attribute */\n",
+       "body .nb { color: #008000 } /* Name.Builtin */\n",
+       "body .nc { color: #0000FF; font-weight: bold } /* Name.Class */\n",
+       "body .no { color: #880000 } /* Name.Constant */\n",
+       "body .nd { color: #AA22FF } /* Name.Decorator */\n",
+       "body .ni { color: #717171; font-weight: bold } /* Name.Entity */\n",
+       "body .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n",
+       "body .nf { color: #0000FF } /* Name.Function */\n",
+       "body .nl { color: #767600 } /* Name.Label */\n",
+       "body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */\n",
+       "body .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
+       "body .nv { color: #19177C } /* Name.Variable */\n",
+       "body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */\n",
+       "body .w { color: #bbbbbb } /* Text.Whitespace */\n",
+       "body .mb { color: #666666 } /* Literal.Number.Bin */\n",
+       "body .mf { color: #666666 } /* Literal.Number.Float */\n",
+       "body .mh { color: #666666 } /* Literal.Number.Hex */\n",
+       "body .mi { color: #666666 } /* Literal.Number.Integer */\n",
+       "body .mo { color: #666666 } /* Literal.Number.Oct */\n",
+       "body .sa { color: #BA2121 } /* Literal.String.Affix */\n",
+       "body .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
+       "body .sc { color: #BA2121 } /* Literal.String.Char */\n",
+       "body .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
+       "body .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
+       "body .s2 { color: #BA2121 } /* Literal.String.Double */\n",
+       "body .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n",
+       "body .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
+       "body .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n",
+       "body .sx { color: #008000 } /* Literal.String.Other */\n",
+       "body .sr { color: #A45A77 } /* Literal.String.Regex */\n",
+       "body .s1 { color: #BA2121 } /* Literal.String.Single */\n",
+       "body .ss { color: #19177C } /* Literal.String.Symbol */\n",
+       "body .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
+       "body .fm { color: #0000FF } /* Name.Function.Magic */\n",
+       "body .vc { color: #19177C } /* Name.Variable.Class */\n",
+       "body .vg { color: #19177C } /* Name.Variable.Global */\n",
+       "body .vi { color: #19177C } /* Name.Variable.Instance */\n",
+       "body .vm { color: #19177C } /* Name.Variable.Magic */\n",
+       "body .il { color: #666666 } /* Literal.Number.Integer.Long */\n",
+       "\n",
+       "  \n",
+       "\n",
+       "\n",
+       "

\n", + "\n", + "
### BEGIN\n",
        "def _test_cell_():\n",
        "\t#| echo: false\n",
        "\ta = "'a' is not polluting global scope"\n",
@@ -219,22 +243,9 @@
        "\tdel _test_cell_\n",
        "_ # This will be added to global scope\n",
        "### END\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", - "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", - "\t\\PY{c+c1}{\\PYZsh{}| echo: false}\n", - "\t\\PY{n}{a} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{a}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{ is not polluting global scope}\\PY{l+s+s2}{\\PYZdq{}}\n", - "\t\\PY{k}{return} \\PY{n}{a} \\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell}\n", - "\\PY{k}{try}\\PY{p}{:}\n", - "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", - "\\PY{k}{finally}\\PY{p}{:}\n", - "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", - "\\PY{n}{\\PYZus{}} \\PY{c+c1}{\\PYZsh{} This will be added to global scope}\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", - "\\end{Verbatim}\n" + "
\n", + "\n", + "\n" ], "text/plain": [ "\n", @@ -298,80 +309,104 @@ { "data": { "text/html": [ - "
### BEGIN\n",
+       "body .hll { background-color: #ffffcc }\n",
+       "body { background: #f8f8f8; }\n",
+       "body .c { color: #3D7B7B; font-style: italic } /* Comment */\n",
+       "body .err { border: 1px solid #FF0000 } /* Error */\n",
+       "body .k { color: #008000; font-weight: bold } /* Keyword */\n",
+       "body .o { color: #666666 } /* Operator */\n",
+       "body .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n",
+       "body .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n",
+       "body .cp { color: #9C6500 } /* Comment.Preproc */\n",
+       "body .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n",
+       "body .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n",
+       "body .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n",
+       "body .gd { color: #A00000 } /* Generic.Deleted */\n",
+       "body .ge { font-style: italic } /* Generic.Emph */\n",
+       "body .gr { color: #E40000 } /* Generic.Error */\n",
+       "body .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
+       "body .gi { color: #008400 } /* Generic.Inserted */\n",
+       "body .go { color: #717171 } /* Generic.Output */\n",
+       "body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
+       "body .gs { font-weight: bold } /* Generic.Strong */\n",
+       "body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
+       "body .gt { color: #0044DD } /* Generic.Traceback */\n",
+       "body .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
+       "body .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
+       "body .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
+       "body .kp { color: #008000 } /* Keyword.Pseudo */\n",
+       "body .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
+       "body .kt { color: #B00040 } /* Keyword.Type */\n",
+       "body .m { color: #666666 } /* Literal.Number */\n",
+       "body .s { color: #BA2121 } /* Literal.String */\n",
+       "body .na { color: #687822 } /* Name.Attribute */\n",
+       "body .nb { color: #008000 } /* Name.Builtin */\n",
+       "body .nc { color: #0000FF; font-weight: bold } /* Name.Class */\n",
+       "body .no { color: #880000 } /* Name.Constant */\n",
+       "body .nd { color: #AA22FF } /* Name.Decorator */\n",
+       "body .ni { color: #717171; font-weight: bold } /* Name.Entity */\n",
+       "body .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n",
+       "body .nf { color: #0000FF } /* Name.Function */\n",
+       "body .nl { color: #767600 } /* Name.Label */\n",
+       "body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */\n",
+       "body .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
+       "body .nv { color: #19177C } /* Name.Variable */\n",
+       "body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */\n",
+       "body .w { color: #bbbbbb } /* Text.Whitespace */\n",
+       "body .mb { color: #666666 } /* Literal.Number.Bin */\n",
+       "body .mf { color: #666666 } /* Literal.Number.Float */\n",
+       "body .mh { color: #666666 } /* Literal.Number.Hex */\n",
+       "body .mi { color: #666666 } /* Literal.Number.Integer */\n",
+       "body .mo { color: #666666 } /* Literal.Number.Oct */\n",
+       "body .sa { color: #BA2121 } /* Literal.String.Affix */\n",
+       "body .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
+       "body .sc { color: #BA2121 } /* Literal.String.Char */\n",
+       "body .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
+       "body .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
+       "body .s2 { color: #BA2121 } /* Literal.String.Double */\n",
+       "body .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n",
+       "body .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
+       "body .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n",
+       "body .sx { color: #008000 } /* Literal.String.Other */\n",
+       "body .sr { color: #A45A77 } /* Literal.String.Regex */\n",
+       "body .s1 { color: #BA2121 } /* Literal.String.Single */\n",
+       "body .ss { color: #19177C } /* Literal.String.Symbol */\n",
+       "body .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
+       "body .fm { color: #0000FF } /* Name.Function.Magic */\n",
+       "body .vc { color: #19177C } /* Name.Variable.Class */\n",
+       "body .vg { color: #19177C } /* Name.Variable.Global */\n",
+       "body .vi { color: #19177C } /* Name.Variable.Instance */\n",
+       "body .vm { color: #19177C } /* Name.Variable.Magic */\n",
+       "body .il { color: #666666 } /* Literal.Number.Integer.Long */\n",
+       "\n",
+       "  \n",
+       "\n",
+       "\n",
+       "

\n", + "\n", + "
### BEGIN\n",
        "def _test_cell_():\n",
        "\t#| echo: false\n",
        "\ta = "'a' is not polluting global scope"\n",
@@ -382,22 +417,9 @@
        "\tdel _test_cell_\n",
        "if _ is not None: display(_)\n",
        "### END\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", - "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", - "\t\\PY{c+c1}{\\PYZsh{}| echo: false}\n", - "\t\\PY{n}{a} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{a}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{ is not polluting global scope}\\PY{l+s+s2}{\\PYZdq{}}\n", - "\t\\PY{k}{return} \\PY{n}{a} \\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell}\n", - "\\PY{k}{try}\\PY{p}{:}\n", - "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", - "\\PY{k}{finally}\\PY{p}{:}\n", - "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", - "\\PY{k}{if} \\PY{n}{\\PYZus{}} \\PY{o+ow}{is} \\PY{o+ow}{not} \\PY{k+kc}{None}\\PY{p}{:} \\PY{n}{display}\\PY{p}{(}\\PY{n}{\\PYZus{}}\\PY{p}{)}\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", - "\\end{Verbatim}\n" + "
\n", + "\n", + "\n" ], "text/plain": [ "\n", @@ -451,80 +473,104 @@ { "data": { "text/html": [ - "
### BEGIN\n",
+       "body .hll { background-color: #ffffcc }\n",
+       "body { background: #f8f8f8; }\n",
+       "body .c { color: #3D7B7B; font-style: italic } /* Comment */\n",
+       "body .err { border: 1px solid #FF0000 } /* Error */\n",
+       "body .k { color: #008000; font-weight: bold } /* Keyword */\n",
+       "body .o { color: #666666 } /* Operator */\n",
+       "body .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n",
+       "body .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n",
+       "body .cp { color: #9C6500 } /* Comment.Preproc */\n",
+       "body .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n",
+       "body .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n",
+       "body .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n",
+       "body .gd { color: #A00000 } /* Generic.Deleted */\n",
+       "body .ge { font-style: italic } /* Generic.Emph */\n",
+       "body .gr { color: #E40000 } /* Generic.Error */\n",
+       "body .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
+       "body .gi { color: #008400 } /* Generic.Inserted */\n",
+       "body .go { color: #717171 } /* Generic.Output */\n",
+       "body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
+       "body .gs { font-weight: bold } /* Generic.Strong */\n",
+       "body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
+       "body .gt { color: #0044DD } /* Generic.Traceback */\n",
+       "body .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
+       "body .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
+       "body .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
+       "body .kp { color: #008000 } /* Keyword.Pseudo */\n",
+       "body .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
+       "body .kt { color: #B00040 } /* Keyword.Type */\n",
+       "body .m { color: #666666 } /* Literal.Number */\n",
+       "body .s { color: #BA2121 } /* Literal.String */\n",
+       "body .na { color: #687822 } /* Name.Attribute */\n",
+       "body .nb { color: #008000 } /* Name.Builtin */\n",
+       "body .nc { color: #0000FF; font-weight: bold } /* Name.Class */\n",
+       "body .no { color: #880000 } /* Name.Constant */\n",
+       "body .nd { color: #AA22FF } /* Name.Decorator */\n",
+       "body .ni { color: #717171; font-weight: bold } /* Name.Entity */\n",
+       "body .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n",
+       "body .nf { color: #0000FF } /* Name.Function */\n",
+       "body .nl { color: #767600 } /* Name.Label */\n",
+       "body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */\n",
+       "body .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
+       "body .nv { color: #19177C } /* Name.Variable */\n",
+       "body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */\n",
+       "body .w { color: #bbbbbb } /* Text.Whitespace */\n",
+       "body .mb { color: #666666 } /* Literal.Number.Bin */\n",
+       "body .mf { color: #666666 } /* Literal.Number.Float */\n",
+       "body .mh { color: #666666 } /* Literal.Number.Hex */\n",
+       "body .mi { color: #666666 } /* Literal.Number.Integer */\n",
+       "body .mo { color: #666666 } /* Literal.Number.Oct */\n",
+       "body .sa { color: #BA2121 } /* Literal.String.Affix */\n",
+       "body .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
+       "body .sc { color: #BA2121 } /* Literal.String.Char */\n",
+       "body .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
+       "body .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
+       "body .s2 { color: #BA2121 } /* Literal.String.Double */\n",
+       "body .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n",
+       "body .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
+       "body .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n",
+       "body .sx { color: #008000 } /* Literal.String.Other */\n",
+       "body .sr { color: #A45A77 } /* Literal.String.Regex */\n",
+       "body .s1 { color: #BA2121 } /* Literal.String.Single */\n",
+       "body .ss { color: #19177C } /* Literal.String.Symbol */\n",
+       "body .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
+       "body .fm { color: #0000FF } /* Name.Function.Magic */\n",
+       "body .vc { color: #19177C } /* Name.Variable.Class */\n",
+       "body .vg { color: #19177C } /* Name.Variable.Global */\n",
+       "body .vi { color: #19177C } /* Name.Variable.Instance */\n",
+       "body .vm { color: #19177C } /* Name.Variable.Magic */\n",
+       "body .il { color: #666666 } /* Literal.Number.Integer.Long */\n",
+       "\n",
+       "  \n",
+       "\n",
+       "\n",
+       "

\n", + "\n", + "
### BEGIN\n",
        "def _test_cell_():\n",
        "\t#| echo: false\n",
        "\ta = "'a' is not polluting global scope"\n",
@@ -535,22 +581,9 @@
        "\tdel _test_cell_\n",
        "_ # This will be added to global scope\n",
        "### END\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", - "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", - "\t\\PY{c+c1}{\\PYZsh{}| echo: false}\n", - "\t\\PY{n}{a} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{a}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{ is not polluting global scope}\\PY{l+s+s2}{\\PYZdq{}}\n", - "\t\\PY{n}{a}\\PY{p}{;}\n", - "\\PY{k}{try}\\PY{p}{:}\n", - "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", - "\\PY{k}{finally}\\PY{p}{:}\n", - "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", - "\\PY{n}{\\PYZus{}} \\PY{c+c1}{\\PYZsh{} This will be added to global scope}\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", - "\\end{Verbatim}\n" + "
\n", + "\n", + "\n" ], "text/plain": [ "\n", @@ -604,80 +637,104 @@ { "data": { "text/html": [ - "
### BEGIN\n",
+       "body .hll { background-color: #ffffcc }\n",
+       "body { background: #f8f8f8; }\n",
+       "body .c { color: #3D7B7B; font-style: italic } /* Comment */\n",
+       "body .err { border: 1px solid #FF0000 } /* Error */\n",
+       "body .k { color: #008000; font-weight: bold } /* Keyword */\n",
+       "body .o { color: #666666 } /* Operator */\n",
+       "body .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n",
+       "body .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n",
+       "body .cp { color: #9C6500 } /* Comment.Preproc */\n",
+       "body .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n",
+       "body .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n",
+       "body .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n",
+       "body .gd { color: #A00000 } /* Generic.Deleted */\n",
+       "body .ge { font-style: italic } /* Generic.Emph */\n",
+       "body .gr { color: #E40000 } /* Generic.Error */\n",
+       "body .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
+       "body .gi { color: #008400 } /* Generic.Inserted */\n",
+       "body .go { color: #717171 } /* Generic.Output */\n",
+       "body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
+       "body .gs { font-weight: bold } /* Generic.Strong */\n",
+       "body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
+       "body .gt { color: #0044DD } /* Generic.Traceback */\n",
+       "body .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
+       "body .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
+       "body .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
+       "body .kp { color: #008000 } /* Keyword.Pseudo */\n",
+       "body .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
+       "body .kt { color: #B00040 } /* Keyword.Type */\n",
+       "body .m { color: #666666 } /* Literal.Number */\n",
+       "body .s { color: #BA2121 } /* Literal.String */\n",
+       "body .na { color: #687822 } /* Name.Attribute */\n",
+       "body .nb { color: #008000 } /* Name.Builtin */\n",
+       "body .nc { color: #0000FF; font-weight: bold } /* Name.Class */\n",
+       "body .no { color: #880000 } /* Name.Constant */\n",
+       "body .nd { color: #AA22FF } /* Name.Decorator */\n",
+       "body .ni { color: #717171; font-weight: bold } /* Name.Entity */\n",
+       "body .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n",
+       "body .nf { color: #0000FF } /* Name.Function */\n",
+       "body .nl { color: #767600 } /* Name.Label */\n",
+       "body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */\n",
+       "body .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
+       "body .nv { color: #19177C } /* Name.Variable */\n",
+       "body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */\n",
+       "body .w { color: #bbbbbb } /* Text.Whitespace */\n",
+       "body .mb { color: #666666 } /* Literal.Number.Bin */\n",
+       "body .mf { color: #666666 } /* Literal.Number.Float */\n",
+       "body .mh { color: #666666 } /* Literal.Number.Hex */\n",
+       "body .mi { color: #666666 } /* Literal.Number.Integer */\n",
+       "body .mo { color: #666666 } /* Literal.Number.Oct */\n",
+       "body .sa { color: #BA2121 } /* Literal.String.Affix */\n",
+       "body .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
+       "body .sc { color: #BA2121 } /* Literal.String.Char */\n",
+       "body .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
+       "body .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
+       "body .s2 { color: #BA2121 } /* Literal.String.Double */\n",
+       "body .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n",
+       "body .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
+       "body .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n",
+       "body .sx { color: #008000 } /* Literal.String.Other */\n",
+       "body .sr { color: #A45A77 } /* Literal.String.Regex */\n",
+       "body .s1 { color: #BA2121 } /* Literal.String.Single */\n",
+       "body .ss { color: #19177C } /* Literal.String.Symbol */\n",
+       "body .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
+       "body .fm { color: #0000FF } /* Name.Function.Magic */\n",
+       "body .vc { color: #19177C } /* Name.Variable.Class */\n",
+       "body .vg { color: #19177C } /* Name.Variable.Global */\n",
+       "body .vi { color: #19177C } /* Name.Variable.Instance */\n",
+       "body .vm { color: #19177C } /* Name.Variable.Magic */\n",
+       "body .il { color: #666666 } /* Literal.Number.Integer.Long */\n",
+       "\n",
+       "  \n",
+       "\n",
+       "\n",
+       "

\n", + "\n", + "
### BEGIN\n",
        "def _test_cell_():\n",
        "\t#| echo: false\n",
        "\ta = "'a' is not polluting global scope"\n",
@@ -688,22 +745,9 @@
        "\tdel _test_cell_\n",
        "_ # This will be added to global scope\n",
        "### END\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", - "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", - "\t\\PY{c+c1}{\\PYZsh{}| echo: false}\n", - "\t\\PY{n}{a} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{a}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{ is not polluting global scope}\\PY{l+s+s2}{\\PYZdq{}}\n", - "\t\\PY{k}{return} \\PY{n+nb}{print}\\PY{p}{(}\\PY{n}{a}\\PY{p}{)} \\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell}\n", - "\\PY{k}{try}\\PY{p}{:}\n", - "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", - "\\PY{k}{finally}\\PY{p}{:}\n", - "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", - "\\PY{n}{\\PYZus{}} \\PY{c+c1}{\\PYZsh{} This will be added to global scope}\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", - "\\end{Verbatim}\n" + "
\n", + "\n", + "\n" ], "text/plain": [ "\n", @@ -766,80 +810,104 @@ { "data": { "text/html": [ - "
### BEGIN\n",
+       "body .hll { background-color: #ffffcc }\n",
+       "body { background: #f8f8f8; }\n",
+       "body .c { color: #3D7B7B; font-style: italic } /* Comment */\n",
+       "body .err { border: 1px solid #FF0000 } /* Error */\n",
+       "body .k { color: #008000; font-weight: bold } /* Keyword */\n",
+       "body .o { color: #666666 } /* Operator */\n",
+       "body .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n",
+       "body .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n",
+       "body .cp { color: #9C6500 } /* Comment.Preproc */\n",
+       "body .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n",
+       "body .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n",
+       "body .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n",
+       "body .gd { color: #A00000 } /* Generic.Deleted */\n",
+       "body .ge { font-style: italic } /* Generic.Emph */\n",
+       "body .gr { color: #E40000 } /* Generic.Error */\n",
+       "body .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
+       "body .gi { color: #008400 } /* Generic.Inserted */\n",
+       "body .go { color: #717171 } /* Generic.Output */\n",
+       "body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
+       "body .gs { font-weight: bold } /* Generic.Strong */\n",
+       "body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
+       "body .gt { color: #0044DD } /* Generic.Traceback */\n",
+       "body .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
+       "body .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
+       "body .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
+       "body .kp { color: #008000 } /* Keyword.Pseudo */\n",
+       "body .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
+       "body .kt { color: #B00040 } /* Keyword.Type */\n",
+       "body .m { color: #666666 } /* Literal.Number */\n",
+       "body .s { color: #BA2121 } /* Literal.String */\n",
+       "body .na { color: #687822 } /* Name.Attribute */\n",
+       "body .nb { color: #008000 } /* Name.Builtin */\n",
+       "body .nc { color: #0000FF; font-weight: bold } /* Name.Class */\n",
+       "body .no { color: #880000 } /* Name.Constant */\n",
+       "body .nd { color: #AA22FF } /* Name.Decorator */\n",
+       "body .ni { color: #717171; font-weight: bold } /* Name.Entity */\n",
+       "body .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n",
+       "body .nf { color: #0000FF } /* Name.Function */\n",
+       "body .nl { color: #767600 } /* Name.Label */\n",
+       "body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */\n",
+       "body .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
+       "body .nv { color: #19177C } /* Name.Variable */\n",
+       "body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */\n",
+       "body .w { color: #bbbbbb } /* Text.Whitespace */\n",
+       "body .mb { color: #666666 } /* Literal.Number.Bin */\n",
+       "body .mf { color: #666666 } /* Literal.Number.Float */\n",
+       "body .mh { color: #666666 } /* Literal.Number.Hex */\n",
+       "body .mi { color: #666666 } /* Literal.Number.Integer */\n",
+       "body .mo { color: #666666 } /* Literal.Number.Oct */\n",
+       "body .sa { color: #BA2121 } /* Literal.String.Affix */\n",
+       "body .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
+       "body .sc { color: #BA2121 } /* Literal.String.Char */\n",
+       "body .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
+       "body .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
+       "body .s2 { color: #BA2121 } /* Literal.String.Double */\n",
+       "body .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n",
+       "body .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
+       "body .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n",
+       "body .sx { color: #008000 } /* Literal.String.Other */\n",
+       "body .sr { color: #A45A77 } /* Literal.String.Regex */\n",
+       "body .s1 { color: #BA2121 } /* Literal.String.Single */\n",
+       "body .ss { color: #19177C } /* Literal.String.Symbol */\n",
+       "body .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
+       "body .fm { color: #0000FF } /* Name.Function.Magic */\n",
+       "body .vc { color: #19177C } /* Name.Variable.Class */\n",
+       "body .vg { color: #19177C } /* Name.Variable.Global */\n",
+       "body .vi { color: #19177C } /* Name.Variable.Instance */\n",
+       "body .vm { color: #19177C } /* Name.Variable.Magic */\n",
+       "body .il { color: #666666 } /* Literal.Number.Integer.Long */\n",
+       "\n",
+       "  \n",
+       "\n",
+       "\n",
+       "

\n", + "\n", + "
### BEGIN\n",
        "def _test_cell_():\n",
        "\t#| echo: false\n",
        "\ta = "'a' is not polluting global scope"\n",
@@ -851,23 +919,9 @@
        "\tdel _test_cell_\n",
        "_ # This will be added to global scope\n",
        "### END\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", - "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", - "\t\\PY{c+c1}{\\PYZsh{}| echo: false}\n", - "\t\\PY{n}{a} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{a}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{ is not polluting global scope}\\PY{l+s+s2}{\\PYZdq{}}\n", - "\t\\PY{k}{return} \\PY{p}{(}\\PY{n}{a}\\PY{p}{,}\n", - "\t \\PY{k+kc}{True}\\PY{p}{)} \\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell}\n", - "\\PY{k}{try}\\PY{p}{:}\n", - "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", - "\\PY{k}{finally}\\PY{p}{:}\n", - "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", - "\\PY{n}{\\PYZus{}} \\PY{c+c1}{\\PYZsh{} This will be added to global scope}\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", - "\\end{Verbatim}\n" + "
\n", + "\n", + "\n" ], "text/plain": [ "\n", @@ -918,6 +972,16 @@ "`testcell.global_skip=True`." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "%%testcell skip\n", + "raise ValueError('This should not be executed')\n", + "```" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1217,6 +1281,359 @@ "aaa" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There is a dedicated syntax to enable passing variables to an isolated context and saving variables of objects back to the main context tith the `(inputs)->(outputs)` syntax" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "%%testcelln (aaa)\n", + "# Input only \n", + "aaa # global variable\n", + "````" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'global variable'" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln (aaa)\n", + "#| echo: false\n", + "# Input only \n", + "aaa # global variable" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "%%testcelln (aaa)->(bbb,ccc) debug\n", + "# Input->Output and debug option\n", + "\n", + "print(aaa) # global variable\n", + "bbb=123\n", + "def ccc(): return 567\n", + "````" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
### BEGIN\n",
+       "def _test_cell_():\n",
+       "\tglobal bbb\n",
+       "\tglobal ccc\n",
+       "\t#| echo: false\n",
+       "\t# Input->Output and debug option\n",
+       "\t\n",
+       "\tprint(aaa) # global variable\n",
+       "\tbbb=123\n",
+       "\tdef ccc(): return 567\n",
+       "try:\n",
+       "\t_ = _test_cell_()\n",
+       "finally:\n",
+       "\tdel _test_cell_\n",
+       "if _ is not None: display(_)\n",
+       "### END\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "\n", + "### BEGIN\n", + "def _test_cell_():\n", + "\tglobal bbb\n", + "\tglobal ccc\n", + "\t#| echo: false\n", + "\t# Input->Output and debug option\n", + "\t\n", + "\tprint(aaa) # global variable\n", + "\tbbb=123\n", + "\tdef ccc(): return 567\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "if _ is not None: display(_)\n", + "### END" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "global variable\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\n", + "
### GLOBALS UPDATE CODE:\n",
+       "global bbb; bbb=locals()["bbb"]\n",
+       "global ccc; ccc=locals()["ccc"]\n",
+       "###\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "\n", + "### GLOBALS UPDATE CODE:\n", + "global bbb; bbb=locals()[\"bbb\"]\n", + "global ccc; ccc=locals()[\"ccc\"]\n", + "###" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln (aaa)->(bbb,ccc) debug\n", + "#| echo: false\n", + "# Input->Output and debug option\n", + "\n", + "print(aaa) # global variable\n", + "bbb=123\n", + "def ccc(): return 567" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Variable bbb has been created in this context\n", + "assert bbb==123\n", + "assert ccc()==567\n", + "\n", + "# cleaup\n", + "del bbb,ccc" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -1278,6 +1695,8 @@ "+ PROJECT PAGE: [https://github.com/artste/testcell](https://github.com/artste/testcell)\n", "+ DOCUMENTATION: [https://artste.github.io/testcell](https://artste.github.io/testcell)\n", "+ PYPI: [https://pypi.org/project/testcell](https://pypi.org/project/testcell)\n", + "+ DETAILED DEMO: [https://github.com/artste/testcell/blob/main/demo/testcell_demo.ipynb]\n", + "+ LAUNCHING BLOG: [https://artste.github.io/blog/posts/introducing-testcell]\n", "+ COLAB DEMO: [testcell_demo.ipynb](https://colab.research.google.com/github/artste/testcell/blob/main/demo/testcell_demo.ipynb)\n", "+ KAGGLE SAMPLE NOTEBOOK: [https://www.kaggle.com/artste/introducing-testcell](https://www.kaggle.com/artste/introducing-testcell)" ] diff --git a/testcell/__init__.py b/testcell/__init__.py index eb5b33d..87ff485 100644 --- a/testcell/__init__.py +++ b/testcell/__init__.py @@ -3,18 +3,19 @@ # %% auto 0 __all__ = ['global_skip', 'global_use_banner', 'params', 'skip_message_box', 'testcell_message_box', 'noglobals_message_box', - 'MessageBox', 'testcell', 'testcelln'] + 'testcell_valid_args', 'TestcellArgs', 'Inout', 'MessageBox', 'Code', 'parse_args', 'parse_testcell_args', + 'testcell', 'testcelln'] # %% ../nbs/02_testcell.ipynb 4 import ast from IPython.core.magic import register_cell_magic, needs_local_scope from IPython import get_ipython # needed for quarto -from IPython.display import Code -# %% ../nbs/02_testcell.ipynb 5 +# %% ../nbs/02_testcell.ipynb 6 from .core import auto_return +from .inout import separate_args_and_inout, process_inout, split_and_strip, validate_and_update_inputs -# %% ../nbs/02_testcell.ipynb 6 +# %% ../nbs/02_testcell.ipynb 7 global_skip = False # If true all %%testcell cells will be skipped global_use_banner = False # if true it shows a banner on the cell's output @@ -25,7 +26,7 @@ 'skip_background':'#808080', 'skip_text':'white', 'skip_emoji':'ℹ️', } -# %% ../nbs/02_testcell.ipynb 8 +# %% ../nbs/02_testcell.ipynb 9 # Temporary class to deal with different display options (jupyter or console) class MessageBox: def __init__(self,data,*,background_color,text_color,emoji=None): @@ -46,15 +47,53 @@ def __repr__(self): if self.emoji is not None: text = self.emoji + " " + text return text -# %% ../nbs/02_testcell.ipynb 9 +# %% ../nbs/02_testcell.ipynb 10 skip_message_box= MessageBox('This cell has been skipped',background_color=params['skip_background'],text_color=params['skip_text'],emoji=params['skip_emoji']) testcell_message_box = MessageBox("testcell",background_color=params['testcell_background'],text_color=params['testcell_text'],emoji=params['testcell_emoji']) noglobals_message_box = MessageBox("testcell noglobals",background_color=params['noglobals_background'],text_color=params['noglobals_text'],emoji=params['noglobals_emoji']) # %% ../nbs/02_testcell.ipynb 13 +from pygments.formatters import HtmlFormatter +from pygments import highlight +from pygments.lexers import PythonLexer +from IPython.display import HTML + +#class Code(data=None, url=None, filename=None, language=None): +# return HTML(highlight(data, PythonLexer(), HtmlFormatter(full=True))) + +class Code: + def __init__(self, data=None, url=None, filename=None, language=None): + self.data = data + # NOTE: skipping other arguments, we're keeling them only for backward compatibility + def _repr_html_(self): return highlight(self.data, PythonLexer(), HtmlFormatter(full=True)) + def __repr__(self): return self.data + +# %% ../nbs/02_testcell.ipynb 18 +testcell_valid_args = {'verbose','dryrun','noglobals','noreturn','skip','banner','debug'} # full commands set + +# %% ../nbs/02_testcell.ipynb 19 +def parse_args(x): + t = set([c for c in split_and_strip(x,' ') if c != '']) + diff = t.difference(testcell_valid_args) + if len(diff)>0: raise ValueError(f'Invalid arguments passed: "{",".join(diff)}"') + return t + +# %% ../nbs/02_testcell.ipynb 22 +from collections import namedtuple +TestcellArgs = namedtuple('TestcellArgs','args inout') +Inout = namedtuple('Inout','input output') + +# %% ../nbs/02_testcell.ipynb 23 +def parse_testcell_args(x:str)->TestcellArgs: + raw_args,raw_inout = separate_args_and_inout(x) + return TestcellArgs(parse_args(raw_args),None if raw_inout is None else Inout(*process_inout(raw_inout))) + +# %% ../nbs/02_testcell.ipynb 26 @register_cell_magic @needs_local_scope def testcell(line, cell, local_ns): + args,inout = parse_testcell_args(line) + # Parse arguments verbose = 'verbose' in line # enable verbose dryrun = 'dryrun' in line # this will avoid running the code and just print out the code like verbose @@ -62,23 +101,37 @@ def testcell(line, cell, local_ns): noreturn = 'noreturn' in line # display but does not return anything, so no memory "footprint" after execution skip = ('skip' in line) or global_skip # skip cell execution use_banner = ('banner' in line) or global_use_banner # shows a contextual banner at top of the cell + debug = 'debug' in args # debug mode shows both code and all the update to the globals # arguments rules if noglobals or dryrun: noreturn=True if dryrun: verbose=True + if debug: verbose=True # skip if skip: display(skip_message_box) return None # exits - # Do the job cell = auto_return(cell) lines = cell.splitlines() + + # Globals management + _globals = {} if noglobals else local_ns + _locals = {'_':None} if noreturn else None # we mask '_' with a local variable to prevent it affecting global scope + # Deal with inout + outputs = None + globals_definitions = [] + if inout is not None: + inputs,outputs = inout.input,inout.output + _globals.update(validate_and_update_inputs(inputs,local_ns)) # inputs + for o in outputs: globals_definitions += ['\t'+f'global {o}'] # outputs + # Wrap inside a function and execute it arr = ['def _test_cell_():'] + arr += globals_definitions arr += ['\t'+x for x in lines] arr += ['try:\n\t_ = _test_cell_()'] # execute it and assign result to '_' arr += ['finally:\n\tdel _test_cell_'] # delete it @@ -88,17 +141,21 @@ def testcell(line, cell, local_ns): arr += ['_ # This will be added to global scope'] # having this as last line makes the same behavior as normal cell wrapped_cell = '\n'.join(arr) + if use_banner: display(noglobals_message_box if noglobals else testcell_message_box) if verbose: display(Code('\n### BEGIN\n'+wrapped_cell+'\n### END',language='python')) - _globals = {} if noglobals else local_ns - _locals = {'_':None} if noreturn else None # we mask '_' with a local variable to prevent it affecting global scope - if not 'dryrun' in line: - if use_banner: display(noglobals_message_box if noglobals else testcell_message_box) - exec(wrapped_cell,_globals,_locals) - + if not dryrun: exec(wrapped_cell,_globals,_locals) + + if (outputs is not None) and (len(outputs)>0) and (not dryrun): + arr2 = [] + for o in outputs: arr2 += [f'global {o}; {o}=locals()["{o}"]'] # this forwards objects to global scope + globals_update_code = '\n'.join(arr2) + if debug: display(Code('\n### GLOBALS UPDATE CODE:\n'+globals_update_code+'\n###',language='python')) + exec(globals_update_code,local_ns,_globals) + return None if noreturn else _globals.get('_',None) # this closes the loop of integration -# %% ../nbs/02_testcell.ipynb 17 +# %% ../nbs/02_testcell.ipynb 30 @register_cell_magic @needs_local_scope def testcelln(line, cell, local_ns): diff --git a/testcell/_modidx.py b/testcell/_modidx.py index c54a55d..8c5445c 100644 --- a/testcell/_modidx.py +++ b/testcell/_modidx.py @@ -15,4 +15,12 @@ 'testcell.core.last_node': ('ast.html#last_node', 'testcell/core.py'), 'testcell.core.last_statement_has_semicolon': ('ast.html#last_statement_has_semicolon', 'testcell/core.py'), 'testcell.core.need_return': ('ast.html#need_return', 'testcell/core.py'), - 'testcell.core.node_source': ('ast.html#node_source', 'testcell/core.py')}}} + 'testcell.core.node_source': ('ast.html#node_source', 'testcell/core.py')}, + 'testcell.inout': { 'testcell.inout.count_char': ('arguments.html#count_char', 'testcell/inout.py'), + 'testcell.inout.count_delta': ('arguments.html#count_delta', 'testcell/inout.py'), + 'testcell.inout.optional_find': ('arguments.html#optional_find', 'testcell/inout.py'), + 'testcell.inout.process_inout': ('arguments.html#process_inout', 'testcell/inout.py'), + 'testcell.inout.separate_args_and_inout': ('arguments.html#separate_args_and_inout', 'testcell/inout.py'), + 'testcell.inout.split_and_strip': ('arguments.html#split_and_strip', 'testcell/inout.py'), + 'testcell.inout.validate_and_update_inputs': ( 'arguments.html#validate_and_update_inputs', + 'testcell/inout.py')}}} diff --git a/testcell/inout.py b/testcell/inout.py new file mode 100644 index 0000000..d382a1b --- /dev/null +++ b/testcell/inout.py @@ -0,0 +1,61 @@ +# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01a_arguments.ipynb. + +# %% auto 0 +__all__ = ['optional_find', 'count_char', 'count_delta', 'split_and_strip', 'process_inout', 'separate_args_and_inout', + 'validate_and_update_inputs'] + +# %% ../nbs/01a_arguments.ipynb 6 +def optional_find(x,cc,reverse=False): + if isinstance(cc,str): cc = [cc] # listify + t = [(x.rfind(c) if reverse else x.find(c),c) for c in cc] + t = [(f,c) for f,c in t if f != -1] + if len(t)==0: return None + return max(t) if reverse else min(t) + +# %% ../nbs/01a_arguments.ipynb 9 +def count_char(x,c): + # Count how many times c appears in x + return sum(map(lambda y: y==c,x)) + +# %% ../nbs/01a_arguments.ipynb 11 +def count_delta(x,a='(',b=')'): + # Return the difference between the count of "a" and "b". + return count_char(x,a) - count_char(x,b) + +# %% ../nbs/01a_arguments.ipynb 14 +def split_and_strip(x,splitter): + t = [t.strip() for t in x.split(splitter)] + if t==['']: return [] + return t + +# %% ../nbs/01a_arguments.ipynb 17 +def process_inout(x,splitter='->'): + if x is None: return None + t = split_and_strip(x,splitter) + for v,c,n in zip(t,map(count_delta,t),map(lambda s: count_char(s,'('),t)): + if n>1: raise ValueError(f'Too much parenthesis on "{v}"') + if c>0: raise ValueError(f'Missing closing parenthesis on "{v}"') + t = [x[1:-1] for x in t] + if len(t)==0: raise ValueError('No groups available') + if len(t)>2: raise ValueError(f'You shouold have only one "{splitter}" symbol') + if len(t)==1: return split_and_strip(t[0],','),[] + if len(t)==2: return split_and_strip(t[0],','),split_and_strip(t[1],',') + +# %% ../nbs/01a_arguments.ipynb 19 +def separate_args_and_inout(x): + if x is None: return None + if (start_t := optional_find(x,['(','->'],reverse=False)) and (end_t := optional_find(x,[')','->'],reverse=True)): + start,_ = start_t + end, c = end_t + length = len(c) + return x[:start]+x[end+length:],x[start:end+length] + return x,None + +# %% ../nbs/01a_arguments.ipynb 22 +def validate_and_update_inputs(inputs:list,state:dict,)->dict: + s = set(inputs) + ret = {} + for k in inputs: + if k not in state: raise ValueError(f'Unable to find object "{k}" in current state') + ret[k] = state[k] + return ret From bc6a8a648a69fca8c43553dbc597fa5f6fee55cc Mon Sep 17 00:00:00 2001 From: Stefano Giomo Date: Tue, 4 Nov 2025 08:39:58 +0000 Subject: [PATCH 03/18] Fixed typos --- README.md | 22 +++++++++++----------- nbs/01a_arguments.ipynb | 2 +- nbs/index.ipynb | 18 +++++++++--------- testcell/inout.py | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 3d17e38..4642c41 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ executed, is wrapped in a temporary function that will be deleted after execution. To give you the feeling of *seamless integration* the last statement is optionally returned like it happens in a normal cell. -**WARNING:** this don’t protect you from *the side effects of your code* -like deleting a file or mutating the state of a global variable. +**WARNING:** this doesn’t protect you from *the side effects of your +code* like deleting a file or mutating the state of a global variable. [![](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/artste/testcell/blob/main/demo/testcell_demo.ipynb) @@ -48,7 +48,7 @@ assert 'a' not in locals() What is happening under the hood is that `%%testcell` wraps your cell’s code with a function, execute it and then deletes it. Adding the -`verbose` keywork will print which code will be executed. +`verbose` keyword will print which code will be executed. NOTE: The actual cell code is enclosed within `BEGIN` and `END` comment blocks for improved readability. @@ -172,7 +172,7 @@ body .il { color: #666666 } /* Literal.Number.Integer.Long */ "'a' is not polluting global scope" If you’re just interested in seeing what will be executed, but actually -not executing it, you ca use `dryrun` option: +not executing it, you can use `dryrun` option: ``` python %%testcell dryrun @@ -409,8 +409,8 @@ body .il { color: #666666 } /* Literal.Number.Integer.Long */ -`testcell` works seamlessly with existing `print` or `display`statements -on last line: +`testcell` works seamlessly with existing `print` or `display` +statements on last line: ``` python %%testcell verbose @@ -657,8 +657,8 @@ body .il { color: #666666 } /* Literal.Number.Integer.Long */ ### Skip execution It is possible to skip a cell execution using `skip` command. This is -usueful when you want to keep around the code but don’t actually run it. -It’s also possible to skip **all cells markked with `%%testcell`** using +useful when you want to keep around the code but don’t actually run it. +It’s also possible to skip **all cells marked with `%%testcell`** using the following syntax: `testcell.global_skip=True`. ``` python @@ -710,7 +710,7 @@ globals().keys() dict_keys(['__builtins__']) -With `%%testcelln` inside the cell, you’ll be able to access only to +With `%%testcelln` inside the cell, you’ll be able to access only `__builtins__` (aka: standard python’s functions). **It behaves like a notebook-in-notebook**. @@ -746,7 +746,7 @@ 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 +running on your main kernel. If you modify variables that have been created outside of this cell (aka: if you have side effects) this will not protect you. @@ -757,7 +757,7 @@ aaa 'global variable' There is a dedicated syntax to enable passing variables to an isolated -context and saving variables of objects back to the main context tith +context and saving variables of objects back to the main context with the `(inputs)->(outputs)` syntax ``` python diff --git a/nbs/01a_arguments.ipynb b/nbs/01a_arguments.ipynb index 63e3072..6d29644 100644 --- a/nbs/01a_arguments.ipynb +++ b/nbs/01a_arguments.ipynb @@ -219,7 +219,7 @@ " if c>0: raise ValueError(f'Missing closing parenthesis on \"{v}\"')\n", " t = [x[1:-1] for x in t]\n", " if len(t)==0: raise ValueError('No groups available')\n", - " if len(t)>2: raise ValueError(f'You shouold have only one \"{splitter}\" symbol')\n", + " if len(t)>2: raise ValueError(f'You should have only one \"{splitter}\" symbol')\n", " if len(t)==1: return split_and_strip(t[0],','),[]\n", " if len(t)==2: return split_and_strip(t[0],','),split_and_strip(t[1],',')" ] diff --git a/nbs/index.ipynb b/nbs/index.ipynb index af7ffe8..3bd971f 100644 --- a/nbs/index.ipynb +++ b/nbs/index.ipynb @@ -27,7 +27,7 @@ "\n", "What's happening under the hood is that your cell code, before being executed, is wrapped in a temporary function that will be deleted after execution. To give you the feeling of *seamless integration* the last statement is optionally returned like it happens in a normal cell.\n", "\n", - "**WARNING:** this don't protect you from *the side effects of your code* like deleting a file or mutating the state of a global variable.\n", + "**WARNING:** this doesn't protect you from *the side effects of your code* like deleting a file or mutating the state of a global variable.\n", "\n", "[![](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/artste/testcell/blob/main/demo/testcell_demo.ipynb) \n", "\n", @@ -111,7 +111,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "What is happening under the hood is that `%%testcell` wraps your cell's code with a function, execute it and then deletes it. Adding the `verbose` keywork will print which code will be executed.\n", + "What is happening under the hood is that `%%testcell` wraps your cell's code with a function, execute it and then deletes it. Adding the `verbose` keyword will print which code will be executed.\n", "\n", "NOTE: The actual cell code is enclosed within `BEGIN` and `END` comment blocks for improved readability." ] @@ -287,7 +287,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "If you're just interested in seeing what will be executed, but actually not executing it, you ca use `dryrun` option:" + "If you're just interested in seeing what will be executed, but actually not executing it, you can use `dryrun` option:" ] }, { @@ -615,7 +615,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "`testcell` works seamlessly with existing `print` or `display`statements on last line:" + "`testcell` works seamlessly with existing `print` or `display` statements on last line:" ] }, { @@ -967,8 +967,8 @@ "metadata": {}, "source": [ "### Skip execution\n", - "It is possible to skip a cell execution using `skip` command. This is usueful when you want to keep around the code but don't actually run it.\n", - "It's also possible to skip **all cells markked with `%%testcell`** using the following syntax: \n", + "It is possible to skip a cell execution using `skip` command. This is useful when you want to keep around the code but don't actually run it.\n", + "It's also possible to skip **all cells marked with `%%testcell`** using the following syntax: \n", "`testcell.global_skip=True`." ] }, @@ -1158,7 +1158,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "With `%%testcelln` inside the cell, you'll be able to access only to `__builtins__` (aka: standard python's functions). **It behaves like a notebook-in-notebook**." + "With `%%testcelln` inside the cell, you'll be able to access only `__builtins__` (aka: standard python's functions). **It behaves like a notebook-in-notebook**." ] }, { @@ -1258,7 +1258,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**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 not protect you." + "**IMPORTANT**: this is *just wrapping your cell* and so it's still running on your main kernel. If you modify variables that have been created outside of this cell (aka: if you have side effects) this will not protect you." ] }, { @@ -1285,7 +1285,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "There is a dedicated syntax to enable passing variables to an isolated context and saving variables of objects back to the main context tith the `(inputs)->(outputs)` syntax" + "There is a dedicated syntax to enable passing variables to an isolated context and saving variables of objects back to the main context with the `(inputs)->(outputs)` syntax" ] }, { diff --git a/testcell/inout.py b/testcell/inout.py index d382a1b..e2b677b 100644 --- a/testcell/inout.py +++ b/testcell/inout.py @@ -37,7 +37,7 @@ def process_inout(x,splitter='->'): if c>0: raise ValueError(f'Missing closing parenthesis on "{v}"') t = [x[1:-1] for x in t] if len(t)==0: raise ValueError('No groups available') - if len(t)>2: raise ValueError(f'You shouold have only one "{splitter}" symbol') + if len(t)>2: raise ValueError(f'You should have only one "{splitter}" symbol') if len(t)==1: return split_and_strip(t[0],','),[] if len(t)==2: return split_and_strip(t[0],','),split_and_strip(t[1],',') From 88299baaf995ffec90be3c56f07e5d7d603d7437 Mon Sep 17 00:00:00 2001 From: Stefano Giomo Date: Tue, 4 Nov 2025 09:41:00 +0000 Subject: [PATCH 04/18] Use Markdown codeblock to represent code instead of html --- nbs/02_testcell.ipynb | 887 ++++++------------------------------------ 1 file changed, 115 insertions(+), 772 deletions(-) diff --git a/nbs/02_testcell.ipynb b/nbs/02_testcell.ipynb index 1a20a97..1bca81f 100644 --- a/nbs/02_testcell.ipynb +++ b/nbs/02_testcell.ipynb @@ -210,20 +210,23 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "raw", "metadata": {}, - "outputs": [], "source": [ - "#| export\n", + "# Skipped because it don't show the \n", + "from IPython.display import Code" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "# Correct colors in jupyter but don't get exported properly and pollutes each cell with a copy of css\n", "from pygments.formatters import HtmlFormatter\n", "from pygments import highlight\n", "from pygments.lexers import PythonLexer\n", "from IPython.display import HTML\n", "\n", - "#class Code(data=None, url=None, filename=None, language=None):\n", - "# return HTML(highlight(data, PythonLexer(), HtmlFormatter(full=True)))\n", - "\n", "class Code:\n", " def __init__(self, data=None, url=None, filename=None, language=None):\n", " self.data = data\n", @@ -232,6 +235,24 @@ " def __repr__(self): return self.data" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "from IPython.display import Markdown\n", + "\n", + "class Code:\n", + " def __init__(self, data=None, url=None, filename=None, language='python'):\n", + " self.data = data\n", + " self.language = language\n", + " # NOTE: skipping other arguments, we're keeling them only for backward compatibility\n", + " def _repr_markdown_(self): return Markdown(f\"```{self.language}\\n{self.data}\\n```\")._repr_markdown_()\n", + " def __repr__(self): return self.data" + ] + }, { "cell_type": "code", "execution_count": null, @@ -239,112 +260,16 @@ "outputs": [ { "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", + "# This is a comment\n", + "class A:\n", + " def b(self): return 1\n", "\n", - "
# This is a comment\n",
-       "class A:\n",
-       "   def b(self): return 1\n",
+       "def c(): return 3\n",
        "\n",
-       "def c(): return 3\n",
-       "
\n", - "\n", - "\n" + "```" ], "text/plain": [ "\n", @@ -1062,118 +987,21 @@ "outputs": [ { "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", + "text/markdown": [ + "```python\n", "\n", - "
### BEGIN\n",
-       "def _test_cell_():\n",
-       "\t# %%testcell verbose\n",
-       "\tb=3\n",
-       "\treturn b # %%testcell\n",
-       "try:\n",
-       "\t_ = _test_cell_()\n",
-       "finally:\n",
-       "\tdel _test_cell_\n",
-       "_ # This will be added to global scope\n",
-       "### END\n",
-       "
\n", - "\n", - "\n" + "### BEGIN\n", + "def _test_cell_():\n", + "\t# %%testcell verbose\n", + "\tb=3\n", + "\treturn b # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "_ # This will be added to global scope\n", + "### END\n", + "```" ], "text/plain": [ "\n", @@ -1227,120 +1055,23 @@ "outputs": [ { "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", + "text/markdown": [ + "```python\n", "\n", - "
### BEGIN\n",
-       "def _test_cell_():\n",
-       "\t# %%testcell dryrun\n",
-       "\tb=1\n",
-       "\tb\n",
+       "### BEGIN\n",
+       "def _test_cell_():\n",
+       "\t# %%testcell dryrun\n",
+       "\tb=1\n",
+       "\tb\n",
        "\t\n",
-       "\tassert False # we should not be here because code is supposed to not be executed\n",
-       "try:\n",
-       "\t_ = _test_cell_()\n",
-       "finally:\n",
-       "\tdel _test_cell_\n",
-       "if _ is not None: display(_)\n",
-       "### END\n",
-       "
\n", - "\n", - "\n" + "\tassert False # we should not be here because code is supposed to not be executed\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "if _ is not None: display(_)\n", + "### END\n", + "```" ], "text/plain": [ "\n", @@ -1595,117 +1326,20 @@ "outputs": [ { "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", + "text/markdown": [ + "```python\n", "\n", - "
### BEGIN\n",
-       "def _test_cell_():\n",
-       "\t# %%testcell verbose\n",
-       "\treturn 'using %%testcell updates the last executed expression result "_"' # %%testcell\n",
-       "try:\n",
-       "\t_ = _test_cell_()\n",
-       "finally:\n",
-       "\tdel _test_cell_\n",
-       "_ # This will be added to global scope\n",
-       "### END\n",
-       "
\n", - "\n", - "\n" + "### BEGIN\n", + "def _test_cell_():\n", + "\t# %%testcell verbose\n", + "\treturn 'using %%testcell updates the last executed expression result \"_\"' # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "_ # This will be added to global scope\n", + "### END\n", + "```" ], "text/plain": [ "\n", @@ -1764,117 +1398,20 @@ "outputs": [ { "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", + "text/markdown": [ + "```python\n", "\n", - "
### BEGIN\n",
-       "def _test_cell_():\n",
-       "\t# %%testcelln\n",
-       "\treturn '%%testcelln does not change "_"' # %%testcell\n",
-       "try:\n",
-       "\t_ = _test_cell_()\n",
-       "finally:\n",
-       "\tdel _test_cell_\n",
-       "if _ is not None: display(_)\n",
-       "### END\n",
-       "
\n", - "\n", - "\n" + "### BEGIN\n", + "def _test_cell_():\n", + "\t# %%testcelln\n", + "\treturn '%%testcelln does not change \"_\"' # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "if _ is not None: display(_)\n", + "### END\n", + "```" ], "text/plain": [ "\n", @@ -2002,124 +1539,27 @@ "outputs": [ { "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", + "text/markdown": [ + "```python\n", "\n", - "
### BEGIN\n",
-       "def _test_cell_():\n",
-       "\tglobal kkk\n",
-       "\tglobal fff\n",
+       "### BEGIN\n",
+       "def _test_cell_():\n",
+       "\tglobal kkk\n",
+       "\tglobal fff\n",
        "\t\n",
-       "\tkkk = 'this is a global variable created inside testcelln cell'\n",
-       "\tdef fff(): \n",
-       "\t    # this is a global function created inside testcelln cell\n",
-       "\t    return my_global_variable # we use a global variable that should not be accessible\n",
+       "\tkkk = 'this is a global variable created inside testcelln cell'\n",
+       "\tdef fff(): \n",
+       "\t    # this is a global function created inside testcelln cell\n",
+       "\t    return my_global_variable # we use a global variable that should not be accessible\n",
        "\t\n",
-       "\treturn fff() # %%testcell\n",
-       "try:\n",
-       "\t_ = _test_cell_()\n",
-       "finally:\n",
-       "\tdel _test_cell_\n",
-       "if _ is not None: display(_)\n",
-       "### END\n",
-       "
\n", - "\n", - "\n" + "\treturn fff() # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "if _ is not None: display(_)\n", + "### END\n", + "```" ], "text/plain": [ "\n", @@ -2156,111 +1596,14 @@ }, { "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", + "text/markdown": [ + "```python\n", "\n", - "
### GLOBALS UPDATE CODE:\n",
-       "global kkk; kkk=locals()["kkk"]\n",
-       "global fff; fff=locals()["fff"]\n",
-       "###\n",
-       "
\n", - "\n", - "\n" + "### GLOBALS UPDATE CODE:\n", + "global kkk; kkk=locals()[\"kkk\"]\n", + "global fff; fff=locals()[\"fff\"]\n", + "###\n", + "```" ], "text/plain": [ "\n", From 05f3a5fb881d0e647dc4ed492732eaf0f408f059 Mon Sep 17 00:00:00 2001 From: Stefano Giomo Date: Tue, 4 Nov 2025 09:42:01 +0000 Subject: [PATCH 05/18] Updated index with new Codeblock, fix 'skip' result in README --- README.md | 773 +------------------------ nbs/index.ipynb | 1299 ++++++++++++++++++++---------------------- nbs/sidebar.yml | 1 + testcell/__init__.py | 27 +- 4 files changed, 633 insertions(+), 1467 deletions(-) diff --git a/README.md b/README.md index 4642c41..9e5022d 100644 --- a/README.md +++ b/README.md @@ -59,116 +59,6 @@ a = "'a' is not polluting global scope" a ``` - - - - - - - - - -

-
### BEGIN
-def _test_cell_():
-    #| echo: false
-    a = "'a' is not polluting global scope"
-    return a # %%testcell
-try:
-    _ = _test_cell_()
-finally:
-    del _test_cell_
-_ # This will be added to global scope
-### END
-
- - - "'a' is not polluting global scope" If you’re just interested in seeing what will be executed, but actually @@ -180,116 +70,6 @@ a = "'a' is not polluting global scope" a ``` - - - - - - - - - -

-
### BEGIN
-def _test_cell_():
-    #| echo: false
-    a = "'a' is not polluting global scope"
-    return a # %%testcell
-try:
-    _ = _test_cell_()
-finally:
-    del _test_cell_
-if _ is not None: display(_)
-### END
-
- - - If you add a semicolon `;` at the end of your last statement no `return` statement is added and nothing is displayed like a normal jupyter cell. @@ -299,116 +79,6 @@ a = "'a' is not polluting global scope" a; ``` - - - - - - - - - -

-
### BEGIN
-def _test_cell_():
-    #| echo: false
-    a = "'a' is not polluting global scope"
-    a;
-try:
-    _ = _test_cell_()
-finally:
-    del _test_cell_
-_ # This will be added to global scope
-### END
-
- - - `testcell` works seamlessly with existing `print` or `display` statements on last line: @@ -418,116 +88,6 @@ a = "'a' is not polluting global scope" print(a) ``` - - - - - - - - - -

-
### BEGIN
-def _test_cell_():
-    #| echo: false
-    a = "'a' is not polluting global scope"
-    return print(a) # %%testcell
-try:
-    _ = _test_cell_()
-finally:
-    del _test_cell_
-_ # This will be added to global scope
-### END
-
- - - 'a' is not polluting global scope Moreover, thanks to `ast`, it properly deals with complex situations @@ -541,117 +101,6 @@ a = "'a' is not polluting global scope" # this is a comment on last line ``` - - - - - - - - - -

-
### BEGIN
-def _test_cell_():
-    #| echo: false
-    a = "'a' is not polluting global scope"
-    return (a,
-     True) # %%testcell
-try:
-    _ = _test_cell_()
-finally:
-    del _test_cell_
-_ # This will be added to global scope
-### END
-
- - - ("'a' is not polluting global scope", True) ### Skip execution @@ -666,10 +115,7 @@ the following syntax: `testcell.global_skip=True`. raise ValueError('This should not be executed') ``` -
- This cell has been skipped -
- + ℹ️ This cell has been skipped ### Run in isolation @@ -777,225 +223,8 @@ bbb=123 def ccc(): return 567 ``` - - - - - - - - - -

-
### BEGIN
-def _test_cell_():
-    global bbb
-    global ccc
-    #| echo: false
-    # Input->Output and debug option
-    
    print(aaa) # global variable
-    bbb=123
-    def ccc(): return 567
-try:
-    _ = _test_cell_()
-finally:
-    del _test_cell_
-if _ is not None: display(_)
-### END
-
- - - global variable - - - - - - - - - -

-
### GLOBALS UPDATE CODE:
-global bbb; bbb=locals()["bbb"]
-global ccc; ccc=locals()["ccc"]
-###
-
- - - ``` python # Variable bbb has been created in this context assert bbb==123 diff --git a/nbs/index.ipynb b/nbs/index.ipynb index 3bd971f..4ce81dc 100644 --- a/nbs/index.ipynb +++ b/nbs/index.ipynb @@ -135,104 +135,80 @@ { "data": { "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
### BEGIN\n",
+       ".output_html .hll { background-color: #ffffcc }\n",
+       ".output_html { background: #f8f8f8; }\n",
+       ".output_html .c { color: #3D7B7B; font-style: italic } /* Comment */\n",
+       ".output_html .err { border: 1px solid #FF0000 } /* Error */\n",
+       ".output_html .k { color: #008000; font-weight: bold } /* Keyword */\n",
+       ".output_html .o { color: #666666 } /* Operator */\n",
+       ".output_html .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n",
+       ".output_html .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n",
+       ".output_html .cp { color: #9C6500 } /* Comment.Preproc */\n",
+       ".output_html .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n",
+       ".output_html .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n",
+       ".output_html .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n",
+       ".output_html .gd { color: #A00000 } /* Generic.Deleted */\n",
+       ".output_html .ge { font-style: italic } /* Generic.Emph */\n",
+       ".output_html .gr { color: #E40000 } /* Generic.Error */\n",
+       ".output_html .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
+       ".output_html .gi { color: #008400 } /* Generic.Inserted */\n",
+       ".output_html .go { color: #717171 } /* Generic.Output */\n",
+       ".output_html .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
+       ".output_html .gs { font-weight: bold } /* Generic.Strong */\n",
+       ".output_html .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
+       ".output_html .gt { color: #0044DD } /* Generic.Traceback */\n",
+       ".output_html .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
+       ".output_html .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
+       ".output_html .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
+       ".output_html .kp { color: #008000 } /* Keyword.Pseudo */\n",
+       ".output_html .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
+       ".output_html .kt { color: #B00040 } /* Keyword.Type */\n",
+       ".output_html .m { color: #666666 } /* Literal.Number */\n",
+       ".output_html .s { color: #BA2121 } /* Literal.String */\n",
+       ".output_html .na { color: #687822 } /* Name.Attribute */\n",
+       ".output_html .nb { color: #008000 } /* Name.Builtin */\n",
+       ".output_html .nc { color: #0000FF; font-weight: bold } /* Name.Class */\n",
+       ".output_html .no { color: #880000 } /* Name.Constant */\n",
+       ".output_html .nd { color: #AA22FF } /* Name.Decorator */\n",
+       ".output_html .ni { color: #717171; font-weight: bold } /* Name.Entity */\n",
+       ".output_html .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n",
+       ".output_html .nf { color: #0000FF } /* Name.Function */\n",
+       ".output_html .nl { color: #767600 } /* Name.Label */\n",
+       ".output_html .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */\n",
+       ".output_html .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
+       ".output_html .nv { color: #19177C } /* Name.Variable */\n",
+       ".output_html .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */\n",
+       ".output_html .w { color: #bbbbbb } /* Text.Whitespace */\n",
+       ".output_html .mb { color: #666666 } /* Literal.Number.Bin */\n",
+       ".output_html .mf { color: #666666 } /* Literal.Number.Float */\n",
+       ".output_html .mh { color: #666666 } /* Literal.Number.Hex */\n",
+       ".output_html .mi { color: #666666 } /* Literal.Number.Integer */\n",
+       ".output_html .mo { color: #666666 } /* Literal.Number.Oct */\n",
+       ".output_html .sa { color: #BA2121 } /* Literal.String.Affix */\n",
+       ".output_html .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
+       ".output_html .sc { color: #BA2121 } /* Literal.String.Char */\n",
+       ".output_html .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
+       ".output_html .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
+       ".output_html .s2 { color: #BA2121 } /* Literal.String.Double */\n",
+       ".output_html .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n",
+       ".output_html .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
+       ".output_html .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n",
+       ".output_html .sx { color: #008000 } /* Literal.String.Other */\n",
+       ".output_html .sr { color: #A45A77 } /* Literal.String.Regex */\n",
+       ".output_html .s1 { color: #BA2121 } /* Literal.String.Single */\n",
+       ".output_html .ss { color: #19177C } /* Literal.String.Symbol */\n",
+       ".output_html .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
+       ".output_html .fm { color: #0000FF } /* Name.Function.Magic */\n",
+       ".output_html .vc { color: #19177C } /* Name.Variable.Class */\n",
+       ".output_html .vg { color: #19177C } /* Name.Variable.Global */\n",
+       ".output_html .vi { color: #19177C } /* Name.Variable.Instance */\n",
+       ".output_html .vm { color: #19177C } /* Name.Variable.Magic */\n",
+       ".output_html .il { color: #666666 } /* Literal.Number.Integer.Long */
### BEGIN\n",
        "def _test_cell_():\n",
        "\t#| echo: false\n",
        "\ta = "'a' is not polluting global scope"\n",
@@ -243,9 +219,22 @@
        "\tdel _test_cell_\n",
        "_ # This will be added to global scope\n",
        "### END\n",
-       "
\n", - "\n", - "\n" + "
\n" + ], + "text/latex": [ + "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", + "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", + "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", + "\t\\PY{c+c1}{\\PYZsh{}| echo: false}\n", + "\t\\PY{n}{a} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{a}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{ is not polluting global scope}\\PY{l+s+s2}{\\PYZdq{}}\n", + "\t\\PY{k}{return} \\PY{n}{a} \\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell}\n", + "\\PY{k}{try}\\PY{p}{:}\n", + "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", + "\\PY{k}{finally}\\PY{p}{:}\n", + "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", + "\\PY{n}{\\PYZus{}} \\PY{c+c1}{\\PYZsh{} This will be added to global scope}\n", + "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", + "\\end{Verbatim}\n" ], "text/plain": [ "\n", @@ -309,104 +298,80 @@ { "data": { "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
### BEGIN\n",
+       ".output_html .hll { background-color: #ffffcc }\n",
+       ".output_html { background: #f8f8f8; }\n",
+       ".output_html .c { color: #3D7B7B; font-style: italic } /* Comment */\n",
+       ".output_html .err { border: 1px solid #FF0000 } /* Error */\n",
+       ".output_html .k { color: #008000; font-weight: bold } /* Keyword */\n",
+       ".output_html .o { color: #666666 } /* Operator */\n",
+       ".output_html .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n",
+       ".output_html .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n",
+       ".output_html .cp { color: #9C6500 } /* Comment.Preproc */\n",
+       ".output_html .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n",
+       ".output_html .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n",
+       ".output_html .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n",
+       ".output_html .gd { color: #A00000 } /* Generic.Deleted */\n",
+       ".output_html .ge { font-style: italic } /* Generic.Emph */\n",
+       ".output_html .gr { color: #E40000 } /* Generic.Error */\n",
+       ".output_html .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
+       ".output_html .gi { color: #008400 } /* Generic.Inserted */\n",
+       ".output_html .go { color: #717171 } /* Generic.Output */\n",
+       ".output_html .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
+       ".output_html .gs { font-weight: bold } /* Generic.Strong */\n",
+       ".output_html .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
+       ".output_html .gt { color: #0044DD } /* Generic.Traceback */\n",
+       ".output_html .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
+       ".output_html .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
+       ".output_html .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
+       ".output_html .kp { color: #008000 } /* Keyword.Pseudo */\n",
+       ".output_html .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
+       ".output_html .kt { color: #B00040 } /* Keyword.Type */\n",
+       ".output_html .m { color: #666666 } /* Literal.Number */\n",
+       ".output_html .s { color: #BA2121 } /* Literal.String */\n",
+       ".output_html .na { color: #687822 } /* Name.Attribute */\n",
+       ".output_html .nb { color: #008000 } /* Name.Builtin */\n",
+       ".output_html .nc { color: #0000FF; font-weight: bold } /* Name.Class */\n",
+       ".output_html .no { color: #880000 } /* Name.Constant */\n",
+       ".output_html .nd { color: #AA22FF } /* Name.Decorator */\n",
+       ".output_html .ni { color: #717171; font-weight: bold } /* Name.Entity */\n",
+       ".output_html .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n",
+       ".output_html .nf { color: #0000FF } /* Name.Function */\n",
+       ".output_html .nl { color: #767600 } /* Name.Label */\n",
+       ".output_html .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */\n",
+       ".output_html .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
+       ".output_html .nv { color: #19177C } /* Name.Variable */\n",
+       ".output_html .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */\n",
+       ".output_html .w { color: #bbbbbb } /* Text.Whitespace */\n",
+       ".output_html .mb { color: #666666 } /* Literal.Number.Bin */\n",
+       ".output_html .mf { color: #666666 } /* Literal.Number.Float */\n",
+       ".output_html .mh { color: #666666 } /* Literal.Number.Hex */\n",
+       ".output_html .mi { color: #666666 } /* Literal.Number.Integer */\n",
+       ".output_html .mo { color: #666666 } /* Literal.Number.Oct */\n",
+       ".output_html .sa { color: #BA2121 } /* Literal.String.Affix */\n",
+       ".output_html .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
+       ".output_html .sc { color: #BA2121 } /* Literal.String.Char */\n",
+       ".output_html .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
+       ".output_html .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
+       ".output_html .s2 { color: #BA2121 } /* Literal.String.Double */\n",
+       ".output_html .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n",
+       ".output_html .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
+       ".output_html .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n",
+       ".output_html .sx { color: #008000 } /* Literal.String.Other */\n",
+       ".output_html .sr { color: #A45A77 } /* Literal.String.Regex */\n",
+       ".output_html .s1 { color: #BA2121 } /* Literal.String.Single */\n",
+       ".output_html .ss { color: #19177C } /* Literal.String.Symbol */\n",
+       ".output_html .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
+       ".output_html .fm { color: #0000FF } /* Name.Function.Magic */\n",
+       ".output_html .vc { color: #19177C } /* Name.Variable.Class */\n",
+       ".output_html .vg { color: #19177C } /* Name.Variable.Global */\n",
+       ".output_html .vi { color: #19177C } /* Name.Variable.Instance */\n",
+       ".output_html .vm { color: #19177C } /* Name.Variable.Magic */\n",
+       ".output_html .il { color: #666666 } /* Literal.Number.Integer.Long */
### BEGIN\n",
        "def _test_cell_():\n",
        "\t#| echo: false\n",
        "\ta = "'a' is not polluting global scope"\n",
@@ -417,9 +382,22 @@
        "\tdel _test_cell_\n",
        "if _ is not None: display(_)\n",
        "### END\n",
-       "
\n", - "\n", - "\n" + "
\n" + ], + "text/latex": [ + "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", + "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", + "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", + "\t\\PY{c+c1}{\\PYZsh{}| echo: false}\n", + "\t\\PY{n}{a} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{a}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{ is not polluting global scope}\\PY{l+s+s2}{\\PYZdq{}}\n", + "\t\\PY{k}{return} \\PY{n}{a} \\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell}\n", + "\\PY{k}{try}\\PY{p}{:}\n", + "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", + "\\PY{k}{finally}\\PY{p}{:}\n", + "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", + "\\PY{k}{if} \\PY{n}{\\PYZus{}} \\PY{o+ow}{is} \\PY{o+ow}{not} \\PY{k+kc}{None}\\PY{p}{:} \\PY{n}{display}\\PY{p}{(}\\PY{n}{\\PYZus{}}\\PY{p}{)}\n", + "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", + "\\end{Verbatim}\n" ], "text/plain": [ "\n", @@ -473,104 +451,80 @@ { "data": { "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
### BEGIN\n",
+       ".output_html .hll { background-color: #ffffcc }\n",
+       ".output_html { background: #f8f8f8; }\n",
+       ".output_html .c { color: #3D7B7B; font-style: italic } /* Comment */\n",
+       ".output_html .err { border: 1px solid #FF0000 } /* Error */\n",
+       ".output_html .k { color: #008000; font-weight: bold } /* Keyword */\n",
+       ".output_html .o { color: #666666 } /* Operator */\n",
+       ".output_html .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n",
+       ".output_html .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n",
+       ".output_html .cp { color: #9C6500 } /* Comment.Preproc */\n",
+       ".output_html .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n",
+       ".output_html .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n",
+       ".output_html .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n",
+       ".output_html .gd { color: #A00000 } /* Generic.Deleted */\n",
+       ".output_html .ge { font-style: italic } /* Generic.Emph */\n",
+       ".output_html .gr { color: #E40000 } /* Generic.Error */\n",
+       ".output_html .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
+       ".output_html .gi { color: #008400 } /* Generic.Inserted */\n",
+       ".output_html .go { color: #717171 } /* Generic.Output */\n",
+       ".output_html .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
+       ".output_html .gs { font-weight: bold } /* Generic.Strong */\n",
+       ".output_html .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
+       ".output_html .gt { color: #0044DD } /* Generic.Traceback */\n",
+       ".output_html .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
+       ".output_html .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
+       ".output_html .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
+       ".output_html .kp { color: #008000 } /* Keyword.Pseudo */\n",
+       ".output_html .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
+       ".output_html .kt { color: #B00040 } /* Keyword.Type */\n",
+       ".output_html .m { color: #666666 } /* Literal.Number */\n",
+       ".output_html .s { color: #BA2121 } /* Literal.String */\n",
+       ".output_html .na { color: #687822 } /* Name.Attribute */\n",
+       ".output_html .nb { color: #008000 } /* Name.Builtin */\n",
+       ".output_html .nc { color: #0000FF; font-weight: bold } /* Name.Class */\n",
+       ".output_html .no { color: #880000 } /* Name.Constant */\n",
+       ".output_html .nd { color: #AA22FF } /* Name.Decorator */\n",
+       ".output_html .ni { color: #717171; font-weight: bold } /* Name.Entity */\n",
+       ".output_html .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n",
+       ".output_html .nf { color: #0000FF } /* Name.Function */\n",
+       ".output_html .nl { color: #767600 } /* Name.Label */\n",
+       ".output_html .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */\n",
+       ".output_html .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
+       ".output_html .nv { color: #19177C } /* Name.Variable */\n",
+       ".output_html .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */\n",
+       ".output_html .w { color: #bbbbbb } /* Text.Whitespace */\n",
+       ".output_html .mb { color: #666666 } /* Literal.Number.Bin */\n",
+       ".output_html .mf { color: #666666 } /* Literal.Number.Float */\n",
+       ".output_html .mh { color: #666666 } /* Literal.Number.Hex */\n",
+       ".output_html .mi { color: #666666 } /* Literal.Number.Integer */\n",
+       ".output_html .mo { color: #666666 } /* Literal.Number.Oct */\n",
+       ".output_html .sa { color: #BA2121 } /* Literal.String.Affix */\n",
+       ".output_html .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
+       ".output_html .sc { color: #BA2121 } /* Literal.String.Char */\n",
+       ".output_html .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
+       ".output_html .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
+       ".output_html .s2 { color: #BA2121 } /* Literal.String.Double */\n",
+       ".output_html .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n",
+       ".output_html .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
+       ".output_html .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n",
+       ".output_html .sx { color: #008000 } /* Literal.String.Other */\n",
+       ".output_html .sr { color: #A45A77 } /* Literal.String.Regex */\n",
+       ".output_html .s1 { color: #BA2121 } /* Literal.String.Single */\n",
+       ".output_html .ss { color: #19177C } /* Literal.String.Symbol */\n",
+       ".output_html .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
+       ".output_html .fm { color: #0000FF } /* Name.Function.Magic */\n",
+       ".output_html .vc { color: #19177C } /* Name.Variable.Class */\n",
+       ".output_html .vg { color: #19177C } /* Name.Variable.Global */\n",
+       ".output_html .vi { color: #19177C } /* Name.Variable.Instance */\n",
+       ".output_html .vm { color: #19177C } /* Name.Variable.Magic */\n",
+       ".output_html .il { color: #666666 } /* Literal.Number.Integer.Long */
### BEGIN\n",
        "def _test_cell_():\n",
        "\t#| echo: false\n",
        "\ta = "'a' is not polluting global scope"\n",
@@ -581,9 +535,22 @@
        "\tdel _test_cell_\n",
        "_ # This will be added to global scope\n",
        "### END\n",
-       "
\n", - "\n", - "\n" + "
\n" + ], + "text/latex": [ + "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", + "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", + "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", + "\t\\PY{c+c1}{\\PYZsh{}| echo: false}\n", + "\t\\PY{n}{a} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{a}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{ is not polluting global scope}\\PY{l+s+s2}{\\PYZdq{}}\n", + "\t\\PY{n}{a}\\PY{p}{;}\n", + "\\PY{k}{try}\\PY{p}{:}\n", + "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", + "\\PY{k}{finally}\\PY{p}{:}\n", + "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", + "\\PY{n}{\\PYZus{}} \\PY{c+c1}{\\PYZsh{} This will be added to global scope}\n", + "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", + "\\end{Verbatim}\n" ], "text/plain": [ "\n", @@ -637,104 +604,80 @@ { "data": { "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
### BEGIN\n",
+       ".output_html .hll { background-color: #ffffcc }\n",
+       ".output_html { background: #f8f8f8; }\n",
+       ".output_html .c { color: #3D7B7B; font-style: italic } /* Comment */\n",
+       ".output_html .err { border: 1px solid #FF0000 } /* Error */\n",
+       ".output_html .k { color: #008000; font-weight: bold } /* Keyword */\n",
+       ".output_html .o { color: #666666 } /* Operator */\n",
+       ".output_html .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n",
+       ".output_html .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n",
+       ".output_html .cp { color: #9C6500 } /* Comment.Preproc */\n",
+       ".output_html .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n",
+       ".output_html .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n",
+       ".output_html .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n",
+       ".output_html .gd { color: #A00000 } /* Generic.Deleted */\n",
+       ".output_html .ge { font-style: italic } /* Generic.Emph */\n",
+       ".output_html .gr { color: #E40000 } /* Generic.Error */\n",
+       ".output_html .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
+       ".output_html .gi { color: #008400 } /* Generic.Inserted */\n",
+       ".output_html .go { color: #717171 } /* Generic.Output */\n",
+       ".output_html .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
+       ".output_html .gs { font-weight: bold } /* Generic.Strong */\n",
+       ".output_html .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
+       ".output_html .gt { color: #0044DD } /* Generic.Traceback */\n",
+       ".output_html .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
+       ".output_html .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
+       ".output_html .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
+       ".output_html .kp { color: #008000 } /* Keyword.Pseudo */\n",
+       ".output_html .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
+       ".output_html .kt { color: #B00040 } /* Keyword.Type */\n",
+       ".output_html .m { color: #666666 } /* Literal.Number */\n",
+       ".output_html .s { color: #BA2121 } /* Literal.String */\n",
+       ".output_html .na { color: #687822 } /* Name.Attribute */\n",
+       ".output_html .nb { color: #008000 } /* Name.Builtin */\n",
+       ".output_html .nc { color: #0000FF; font-weight: bold } /* Name.Class */\n",
+       ".output_html .no { color: #880000 } /* Name.Constant */\n",
+       ".output_html .nd { color: #AA22FF } /* Name.Decorator */\n",
+       ".output_html .ni { color: #717171; font-weight: bold } /* Name.Entity */\n",
+       ".output_html .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n",
+       ".output_html .nf { color: #0000FF } /* Name.Function */\n",
+       ".output_html .nl { color: #767600 } /* Name.Label */\n",
+       ".output_html .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */\n",
+       ".output_html .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
+       ".output_html .nv { color: #19177C } /* Name.Variable */\n",
+       ".output_html .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */\n",
+       ".output_html .w { color: #bbbbbb } /* Text.Whitespace */\n",
+       ".output_html .mb { color: #666666 } /* Literal.Number.Bin */\n",
+       ".output_html .mf { color: #666666 } /* Literal.Number.Float */\n",
+       ".output_html .mh { color: #666666 } /* Literal.Number.Hex */\n",
+       ".output_html .mi { color: #666666 } /* Literal.Number.Integer */\n",
+       ".output_html .mo { color: #666666 } /* Literal.Number.Oct */\n",
+       ".output_html .sa { color: #BA2121 } /* Literal.String.Affix */\n",
+       ".output_html .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
+       ".output_html .sc { color: #BA2121 } /* Literal.String.Char */\n",
+       ".output_html .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
+       ".output_html .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
+       ".output_html .s2 { color: #BA2121 } /* Literal.String.Double */\n",
+       ".output_html .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n",
+       ".output_html .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
+       ".output_html .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n",
+       ".output_html .sx { color: #008000 } /* Literal.String.Other */\n",
+       ".output_html .sr { color: #A45A77 } /* Literal.String.Regex */\n",
+       ".output_html .s1 { color: #BA2121 } /* Literal.String.Single */\n",
+       ".output_html .ss { color: #19177C } /* Literal.String.Symbol */\n",
+       ".output_html .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
+       ".output_html .fm { color: #0000FF } /* Name.Function.Magic */\n",
+       ".output_html .vc { color: #19177C } /* Name.Variable.Class */\n",
+       ".output_html .vg { color: #19177C } /* Name.Variable.Global */\n",
+       ".output_html .vi { color: #19177C } /* Name.Variable.Instance */\n",
+       ".output_html .vm { color: #19177C } /* Name.Variable.Magic */\n",
+       ".output_html .il { color: #666666 } /* Literal.Number.Integer.Long */
### BEGIN\n",
        "def _test_cell_():\n",
        "\t#| echo: false\n",
        "\ta = "'a' is not polluting global scope"\n",
@@ -745,9 +688,22 @@
        "\tdel _test_cell_\n",
        "_ # This will be added to global scope\n",
        "### END\n",
-       "
\n", - "\n", - "\n" + "
\n" + ], + "text/latex": [ + "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", + "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", + "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", + "\t\\PY{c+c1}{\\PYZsh{}| echo: false}\n", + "\t\\PY{n}{a} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{a}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{ is not polluting global scope}\\PY{l+s+s2}{\\PYZdq{}}\n", + "\t\\PY{k}{return} \\PY{n+nb}{print}\\PY{p}{(}\\PY{n}{a}\\PY{p}{)} \\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell}\n", + "\\PY{k}{try}\\PY{p}{:}\n", + "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", + "\\PY{k}{finally}\\PY{p}{:}\n", + "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", + "\\PY{n}{\\PYZus{}} \\PY{c+c1}{\\PYZsh{} This will be added to global scope}\n", + "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", + "\\end{Verbatim}\n" ], "text/plain": [ "\n", @@ -810,104 +766,80 @@ { "data": { "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
### BEGIN\n",
+       ".output_html .hll { background-color: #ffffcc }\n",
+       ".output_html { background: #f8f8f8; }\n",
+       ".output_html .c { color: #3D7B7B; font-style: italic } /* Comment */\n",
+       ".output_html .err { border: 1px solid #FF0000 } /* Error */\n",
+       ".output_html .k { color: #008000; font-weight: bold } /* Keyword */\n",
+       ".output_html .o { color: #666666 } /* Operator */\n",
+       ".output_html .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n",
+       ".output_html .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n",
+       ".output_html .cp { color: #9C6500 } /* Comment.Preproc */\n",
+       ".output_html .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n",
+       ".output_html .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n",
+       ".output_html .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n",
+       ".output_html .gd { color: #A00000 } /* Generic.Deleted */\n",
+       ".output_html .ge { font-style: italic } /* Generic.Emph */\n",
+       ".output_html .gr { color: #E40000 } /* Generic.Error */\n",
+       ".output_html .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
+       ".output_html .gi { color: #008400 } /* Generic.Inserted */\n",
+       ".output_html .go { color: #717171 } /* Generic.Output */\n",
+       ".output_html .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
+       ".output_html .gs { font-weight: bold } /* Generic.Strong */\n",
+       ".output_html .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
+       ".output_html .gt { color: #0044DD } /* Generic.Traceback */\n",
+       ".output_html .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
+       ".output_html .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
+       ".output_html .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
+       ".output_html .kp { color: #008000 } /* Keyword.Pseudo */\n",
+       ".output_html .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
+       ".output_html .kt { color: #B00040 } /* Keyword.Type */\n",
+       ".output_html .m { color: #666666 } /* Literal.Number */\n",
+       ".output_html .s { color: #BA2121 } /* Literal.String */\n",
+       ".output_html .na { color: #687822 } /* Name.Attribute */\n",
+       ".output_html .nb { color: #008000 } /* Name.Builtin */\n",
+       ".output_html .nc { color: #0000FF; font-weight: bold } /* Name.Class */\n",
+       ".output_html .no { color: #880000 } /* Name.Constant */\n",
+       ".output_html .nd { color: #AA22FF } /* Name.Decorator */\n",
+       ".output_html .ni { color: #717171; font-weight: bold } /* Name.Entity */\n",
+       ".output_html .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n",
+       ".output_html .nf { color: #0000FF } /* Name.Function */\n",
+       ".output_html .nl { color: #767600 } /* Name.Label */\n",
+       ".output_html .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */\n",
+       ".output_html .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
+       ".output_html .nv { color: #19177C } /* Name.Variable */\n",
+       ".output_html .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */\n",
+       ".output_html .w { color: #bbbbbb } /* Text.Whitespace */\n",
+       ".output_html .mb { color: #666666 } /* Literal.Number.Bin */\n",
+       ".output_html .mf { color: #666666 } /* Literal.Number.Float */\n",
+       ".output_html .mh { color: #666666 } /* Literal.Number.Hex */\n",
+       ".output_html .mi { color: #666666 } /* Literal.Number.Integer */\n",
+       ".output_html .mo { color: #666666 } /* Literal.Number.Oct */\n",
+       ".output_html .sa { color: #BA2121 } /* Literal.String.Affix */\n",
+       ".output_html .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
+       ".output_html .sc { color: #BA2121 } /* Literal.String.Char */\n",
+       ".output_html .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
+       ".output_html .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
+       ".output_html .s2 { color: #BA2121 } /* Literal.String.Double */\n",
+       ".output_html .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n",
+       ".output_html .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
+       ".output_html .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n",
+       ".output_html .sx { color: #008000 } /* Literal.String.Other */\n",
+       ".output_html .sr { color: #A45A77 } /* Literal.String.Regex */\n",
+       ".output_html .s1 { color: #BA2121 } /* Literal.String.Single */\n",
+       ".output_html .ss { color: #19177C } /* Literal.String.Symbol */\n",
+       ".output_html .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
+       ".output_html .fm { color: #0000FF } /* Name.Function.Magic */\n",
+       ".output_html .vc { color: #19177C } /* Name.Variable.Class */\n",
+       ".output_html .vg { color: #19177C } /* Name.Variable.Global */\n",
+       ".output_html .vi { color: #19177C } /* Name.Variable.Instance */\n",
+       ".output_html .vm { color: #19177C } /* Name.Variable.Magic */\n",
+       ".output_html .il { color: #666666 } /* Literal.Number.Integer.Long */
### BEGIN\n",
        "def _test_cell_():\n",
        "\t#| echo: false\n",
        "\ta = "'a' is not polluting global scope"\n",
@@ -919,9 +851,23 @@
        "\tdel _test_cell_\n",
        "_ # This will be added to global scope\n",
        "### END\n",
-       "
\n", - "\n", - "\n" + "
\n" + ], + "text/latex": [ + "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", + "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", + "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", + "\t\\PY{c+c1}{\\PYZsh{}| echo: false}\n", + "\t\\PY{n}{a} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{a}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{ is not polluting global scope}\\PY{l+s+s2}{\\PYZdq{}}\n", + "\t\\PY{k}{return} \\PY{p}{(}\\PY{n}{a}\\PY{p}{,}\n", + "\t \\PY{k+kc}{True}\\PY{p}{)} \\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell}\n", + "\\PY{k}{try}\\PY{p}{:}\n", + "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", + "\\PY{k}{finally}\\PY{p}{:}\n", + "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", + "\\PY{n}{\\PYZus{}} \\PY{c+c1}{\\PYZsh{} This will be added to global scope}\n", + "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", + "\\end{Verbatim}\n" ], "text/plain": [ "\n", @@ -1007,9 +953,28 @@ "source": [ "%%testcell skip\n", "#| echo: false\n", + "#| output: false\n", "raise ValueError('This should not be executed')" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ℹ️ This cell has been skipped\n" + ] + } + ], + "source": [ + "#| echo: false\n", + "print(testcell.skip_message_box.__repr__())" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -1343,104 +1308,80 @@ { "data": { "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
### BEGIN\n",
+       ".output_html .hll { background-color: #ffffcc }\n",
+       ".output_html { background: #f8f8f8; }\n",
+       ".output_html .c { color: #3D7B7B; font-style: italic } /* Comment */\n",
+       ".output_html .err { border: 1px solid #FF0000 } /* Error */\n",
+       ".output_html .k { color: #008000; font-weight: bold } /* Keyword */\n",
+       ".output_html .o { color: #666666 } /* Operator */\n",
+       ".output_html .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n",
+       ".output_html .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n",
+       ".output_html .cp { color: #9C6500 } /* Comment.Preproc */\n",
+       ".output_html .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n",
+       ".output_html .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n",
+       ".output_html .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n",
+       ".output_html .gd { color: #A00000 } /* Generic.Deleted */\n",
+       ".output_html .ge { font-style: italic } /* Generic.Emph */\n",
+       ".output_html .gr { color: #E40000 } /* Generic.Error */\n",
+       ".output_html .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
+       ".output_html .gi { color: #008400 } /* Generic.Inserted */\n",
+       ".output_html .go { color: #717171 } /* Generic.Output */\n",
+       ".output_html .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
+       ".output_html .gs { font-weight: bold } /* Generic.Strong */\n",
+       ".output_html .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
+       ".output_html .gt { color: #0044DD } /* Generic.Traceback */\n",
+       ".output_html .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
+       ".output_html .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
+       ".output_html .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
+       ".output_html .kp { color: #008000 } /* Keyword.Pseudo */\n",
+       ".output_html .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
+       ".output_html .kt { color: #B00040 } /* Keyword.Type */\n",
+       ".output_html .m { color: #666666 } /* Literal.Number */\n",
+       ".output_html .s { color: #BA2121 } /* Literal.String */\n",
+       ".output_html .na { color: #687822 } /* Name.Attribute */\n",
+       ".output_html .nb { color: #008000 } /* Name.Builtin */\n",
+       ".output_html .nc { color: #0000FF; font-weight: bold } /* Name.Class */\n",
+       ".output_html .no { color: #880000 } /* Name.Constant */\n",
+       ".output_html .nd { color: #AA22FF } /* Name.Decorator */\n",
+       ".output_html .ni { color: #717171; font-weight: bold } /* Name.Entity */\n",
+       ".output_html .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n",
+       ".output_html .nf { color: #0000FF } /* Name.Function */\n",
+       ".output_html .nl { color: #767600 } /* Name.Label */\n",
+       ".output_html .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */\n",
+       ".output_html .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
+       ".output_html .nv { color: #19177C } /* Name.Variable */\n",
+       ".output_html .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */\n",
+       ".output_html .w { color: #bbbbbb } /* Text.Whitespace */\n",
+       ".output_html .mb { color: #666666 } /* Literal.Number.Bin */\n",
+       ".output_html .mf { color: #666666 } /* Literal.Number.Float */\n",
+       ".output_html .mh { color: #666666 } /* Literal.Number.Hex */\n",
+       ".output_html .mi { color: #666666 } /* Literal.Number.Integer */\n",
+       ".output_html .mo { color: #666666 } /* Literal.Number.Oct */\n",
+       ".output_html .sa { color: #BA2121 } /* Literal.String.Affix */\n",
+       ".output_html .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
+       ".output_html .sc { color: #BA2121 } /* Literal.String.Char */\n",
+       ".output_html .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
+       ".output_html .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
+       ".output_html .s2 { color: #BA2121 } /* Literal.String.Double */\n",
+       ".output_html .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n",
+       ".output_html .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
+       ".output_html .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n",
+       ".output_html .sx { color: #008000 } /* Literal.String.Other */\n",
+       ".output_html .sr { color: #A45A77 } /* Literal.String.Regex */\n",
+       ".output_html .s1 { color: #BA2121 } /* Literal.String.Single */\n",
+       ".output_html .ss { color: #19177C } /* Literal.String.Symbol */\n",
+       ".output_html .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
+       ".output_html .fm { color: #0000FF } /* Name.Function.Magic */\n",
+       ".output_html .vc { color: #19177C } /* Name.Variable.Class */\n",
+       ".output_html .vg { color: #19177C } /* Name.Variable.Global */\n",
+       ".output_html .vi { color: #19177C } /* Name.Variable.Instance */\n",
+       ".output_html .vm { color: #19177C } /* Name.Variable.Magic */\n",
+       ".output_html .il { color: #666666 } /* Literal.Number.Integer.Long */
### BEGIN\n",
        "def _test_cell_():\n",
        "\tglobal bbb\n",
        "\tglobal ccc\n",
@@ -1456,9 +1397,27 @@
        "\tdel _test_cell_\n",
        "if _ is not None: display(_)\n",
        "### END\n",
-       "
\n", - "\n", - "\n" + "
\n" + ], + "text/latex": [ + "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", + "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", + "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", + "\t\\PY{k}{global} \\PY{n}{bbb}\n", + "\t\\PY{k}{global} \\PY{n}{ccc}\n", + "\t\\PY{c+c1}{\\PYZsh{}| echo: false}\n", + "\t\\PY{c+c1}{\\PYZsh{} Input\\PYZhy{}\\PYZgt{}Output and debug option}\n", + "\t\n", + "\t\\PY{n+nb}{print}\\PY{p}{(}\\PY{n}{aaa}\\PY{p}{)} \\PY{c+c1}{\\PYZsh{} global variable}\n", + "\t\\PY{n}{bbb}\\PY{o}{=}\\PY{l+m+mi}{123}\n", + "\t\\PY{k}{def} \\PY{n+nf}{ccc}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:} \\PY{k}{return} \\PY{l+m+mi}{567}\n", + "\\PY{k}{try}\\PY{p}{:}\n", + "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", + "\\PY{k}{finally}\\PY{p}{:}\n", + "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", + "\\PY{k}{if} \\PY{n}{\\PYZus{}} \\PY{o+ow}{is} \\PY{o+ow}{not} \\PY{k+kc}{None}\\PY{p}{:} \\PY{n}{display}\\PY{p}{(}\\PY{n}{\\PYZus{}}\\PY{p}{)}\n", + "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", + "\\end{Verbatim}\n" ], "text/plain": [ "\n", @@ -1493,110 +1452,92 @@ { "data": { "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
### GLOBALS UPDATE CODE:\n",
+       ".output_html .hll { background-color: #ffffcc }\n",
+       ".output_html { background: #f8f8f8; }\n",
+       ".output_html .c { color: #3D7B7B; font-style: italic } /* Comment */\n",
+       ".output_html .err { border: 1px solid #FF0000 } /* Error */\n",
+       ".output_html .k { color: #008000; font-weight: bold } /* Keyword */\n",
+       ".output_html .o { color: #666666 } /* Operator */\n",
+       ".output_html .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n",
+       ".output_html .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n",
+       ".output_html .cp { color: #9C6500 } /* Comment.Preproc */\n",
+       ".output_html .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n",
+       ".output_html .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n",
+       ".output_html .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n",
+       ".output_html .gd { color: #A00000 } /* Generic.Deleted */\n",
+       ".output_html .ge { font-style: italic } /* Generic.Emph */\n",
+       ".output_html .gr { color: #E40000 } /* Generic.Error */\n",
+       ".output_html .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n",
+       ".output_html .gi { color: #008400 } /* Generic.Inserted */\n",
+       ".output_html .go { color: #717171 } /* Generic.Output */\n",
+       ".output_html .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n",
+       ".output_html .gs { font-weight: bold } /* Generic.Strong */\n",
+       ".output_html .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n",
+       ".output_html .gt { color: #0044DD } /* Generic.Traceback */\n",
+       ".output_html .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n",
+       ".output_html .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n",
+       ".output_html .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n",
+       ".output_html .kp { color: #008000 } /* Keyword.Pseudo */\n",
+       ".output_html .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n",
+       ".output_html .kt { color: #B00040 } /* Keyword.Type */\n",
+       ".output_html .m { color: #666666 } /* Literal.Number */\n",
+       ".output_html .s { color: #BA2121 } /* Literal.String */\n",
+       ".output_html .na { color: #687822 } /* Name.Attribute */\n",
+       ".output_html .nb { color: #008000 } /* Name.Builtin */\n",
+       ".output_html .nc { color: #0000FF; font-weight: bold } /* Name.Class */\n",
+       ".output_html .no { color: #880000 } /* Name.Constant */\n",
+       ".output_html .nd { color: #AA22FF } /* Name.Decorator */\n",
+       ".output_html .ni { color: #717171; font-weight: bold } /* Name.Entity */\n",
+       ".output_html .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n",
+       ".output_html .nf { color: #0000FF } /* Name.Function */\n",
+       ".output_html .nl { color: #767600 } /* Name.Label */\n",
+       ".output_html .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */\n",
+       ".output_html .nt { color: #008000; font-weight: bold } /* Name.Tag */\n",
+       ".output_html .nv { color: #19177C } /* Name.Variable */\n",
+       ".output_html .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */\n",
+       ".output_html .w { color: #bbbbbb } /* Text.Whitespace */\n",
+       ".output_html .mb { color: #666666 } /* Literal.Number.Bin */\n",
+       ".output_html .mf { color: #666666 } /* Literal.Number.Float */\n",
+       ".output_html .mh { color: #666666 } /* Literal.Number.Hex */\n",
+       ".output_html .mi { color: #666666 } /* Literal.Number.Integer */\n",
+       ".output_html .mo { color: #666666 } /* Literal.Number.Oct */\n",
+       ".output_html .sa { color: #BA2121 } /* Literal.String.Affix */\n",
+       ".output_html .sb { color: #BA2121 } /* Literal.String.Backtick */\n",
+       ".output_html .sc { color: #BA2121 } /* Literal.String.Char */\n",
+       ".output_html .dl { color: #BA2121 } /* Literal.String.Delimiter */\n",
+       ".output_html .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n",
+       ".output_html .s2 { color: #BA2121 } /* Literal.String.Double */\n",
+       ".output_html .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n",
+       ".output_html .sh { color: #BA2121 } /* Literal.String.Heredoc */\n",
+       ".output_html .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n",
+       ".output_html .sx { color: #008000 } /* Literal.String.Other */\n",
+       ".output_html .sr { color: #A45A77 } /* Literal.String.Regex */\n",
+       ".output_html .s1 { color: #BA2121 } /* Literal.String.Single */\n",
+       ".output_html .ss { color: #19177C } /* Literal.String.Symbol */\n",
+       ".output_html .bp { color: #008000 } /* Name.Builtin.Pseudo */\n",
+       ".output_html .fm { color: #0000FF } /* Name.Function.Magic */\n",
+       ".output_html .vc { color: #19177C } /* Name.Variable.Class */\n",
+       ".output_html .vg { color: #19177C } /* Name.Variable.Global */\n",
+       ".output_html .vi { color: #19177C } /* Name.Variable.Instance */\n",
+       ".output_html .vm { color: #19177C } /* Name.Variable.Magic */\n",
+       ".output_html .il { color: #666666 } /* Literal.Number.Integer.Long */
### GLOBALS UPDATE CODE:\n",
        "global bbb; bbb=locals()["bbb"]\n",
        "global ccc; ccc=locals()["ccc"]\n",
        "###\n",
-       "
\n", - "\n", - "\n" + "
\n" + ], + "text/latex": [ + "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", + "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} GLOBALS UPDATE CODE:}\n", + "\\PY{k}{global} \\PY{n}{bbb}\\PY{p}{;} \\PY{n}{bbb}\\PY{o}{=}\\PY{n+nb}{locals}\\PY{p}{(}\\PY{p}{)}\\PY{p}{[}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{bbb}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{]}\n", + "\\PY{k}{global} \\PY{n}{ccc}\\PY{p}{;} \\PY{n}{ccc}\\PY{o}{=}\\PY{n+nb}{locals}\\PY{p}{(}\\PY{p}{)}\\PY{p}{[}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{ccc}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{]}\n", + "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{}}\n", + "\\end{Verbatim}\n" ], "text/plain": [ "\n", diff --git a/nbs/sidebar.yml b/nbs/sidebar.yml index b3f3257..7b5ea2e 100644 --- a/nbs/sidebar.yml +++ b/nbs/sidebar.yml @@ -3,4 +3,5 @@ website: contents: - index.ipynb - 01_ast.ipynb + - 01a_arguments.ipynb - 02_testcell.ipynb diff --git a/testcell/__init__.py b/testcell/__init__.py index 87ff485..a693b46 100644 --- a/testcell/__init__.py +++ b/testcell/__init__.py @@ -52,43 +52,38 @@ def __repr__(self): testcell_message_box = MessageBox("testcell",background_color=params['testcell_background'],text_color=params['testcell_text'],emoji=params['testcell_emoji']) noglobals_message_box = MessageBox("testcell noglobals",background_color=params['noglobals_background'],text_color=params['noglobals_text'],emoji=params['noglobals_emoji']) -# %% ../nbs/02_testcell.ipynb 13 -from pygments.formatters import HtmlFormatter -from pygments import highlight -from pygments.lexers import PythonLexer -from IPython.display import HTML - -#class Code(data=None, url=None, filename=None, language=None): -# return HTML(highlight(data, PythonLexer(), HtmlFormatter(full=True))) +# %% ../nbs/02_testcell.ipynb 15 +from IPython.display import Markdown class Code: - def __init__(self, data=None, url=None, filename=None, language=None): + def __init__(self, data=None, url=None, filename=None, language='python'): self.data = data + self.language = language # NOTE: skipping other arguments, we're keeling them only for backward compatibility - def _repr_html_(self): return highlight(self.data, PythonLexer(), HtmlFormatter(full=True)) + def _repr_markdown_(self): return Markdown(f"```{self.language}\n{self.data}\n```")._repr_markdown_() def __repr__(self): return self.data -# %% ../nbs/02_testcell.ipynb 18 +# %% ../nbs/02_testcell.ipynb 20 testcell_valid_args = {'verbose','dryrun','noglobals','noreturn','skip','banner','debug'} # full commands set -# %% ../nbs/02_testcell.ipynb 19 +# %% ../nbs/02_testcell.ipynb 21 def parse_args(x): t = set([c for c in split_and_strip(x,' ') if c != '']) diff = t.difference(testcell_valid_args) if len(diff)>0: raise ValueError(f'Invalid arguments passed: "{",".join(diff)}"') return t -# %% ../nbs/02_testcell.ipynb 22 +# %% ../nbs/02_testcell.ipynb 24 from collections import namedtuple TestcellArgs = namedtuple('TestcellArgs','args inout') Inout = namedtuple('Inout','input output') -# %% ../nbs/02_testcell.ipynb 23 +# %% ../nbs/02_testcell.ipynb 25 def parse_testcell_args(x:str)->TestcellArgs: raw_args,raw_inout = separate_args_and_inout(x) return TestcellArgs(parse_args(raw_args),None if raw_inout is None else Inout(*process_inout(raw_inout))) -# %% ../nbs/02_testcell.ipynb 26 +# %% ../nbs/02_testcell.ipynb 28 @register_cell_magic @needs_local_scope def testcell(line, cell, local_ns): @@ -155,7 +150,7 @@ def testcell(line, cell, local_ns): return None if noreturn else _globals.get('_',None) # this closes the loop of integration -# %% ../nbs/02_testcell.ipynb 30 +# %% ../nbs/02_testcell.ipynb 32 @register_cell_magic @needs_local_scope def testcelln(line, cell, local_ns): From b1c21cba1c25b16ae9f52c45b09b23091247bffc Mon Sep 17 00:00:00 2001 From: Stefano Giomo Date: Wed, 5 Nov 2025 09:17:16 +0000 Subject: [PATCH 06/18] New cell'soutput, typo, updated links. --- README.md | 116 ++++++- nbs/index.ipynb | 824 +++++++----------------------------------------- 2 files changed, 228 insertions(+), 712 deletions(-) diff --git a/README.md b/README.md index 9e5022d..31b57b6 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,13 @@ pip install testcell ## How to use -just import it with `import testcell` and then use the `%%testcell` cell +Just import it with `import testcell` and then use the `%%testcell` cell magic. +``` python +import testcell +``` + ``` python %%testcell a = "'a' is not polluting global scope" @@ -57,6 +61,21 @@ blocks for improved readability. %%testcell verbose a = "'a' is not polluting global scope" a +``` + +``` python + +### BEGIN +def _test_cell_(): + #| echo: false + a = "'a' is not polluting global scope" + return a # %%testcell +try: + _ = _test_cell_() +finally: + del _test_cell_ +_ # This will be added to global scope +### END ``` "'a' is not polluting global scope" @@ -70,6 +89,21 @@ a = "'a' is not polluting global scope" a ``` +``` python + +### BEGIN +def _test_cell_(): + #| echo: false + a = "'a' is not polluting global scope" + return a # %%testcell +try: + _ = _test_cell_() +finally: + del _test_cell_ +if _ is not None: display(_) +### END +``` + If you add a semicolon `;` at the end of your last statement no `return` statement is added and nothing is displayed like a normal jupyter cell. @@ -79,6 +113,21 @@ a = "'a' is not polluting global scope" a; ``` +``` python + +### BEGIN +def _test_cell_(): + #| echo: false + a = "'a' is not polluting global scope" + a; +try: + _ = _test_cell_() +finally: + del _test_cell_ +_ # This will be added to global scope +### END +``` + `testcell` works seamlessly with existing `print` or `display` statements on last line: @@ -86,6 +135,21 @@ statements on last line: %%testcell verbose a = "'a' is not polluting global scope" print(a) +``` + +``` python + +### BEGIN +def _test_cell_(): + #| echo: false + a = "'a' is not polluting global scope" + return print(a) # %%testcell +try: + _ = _test_cell_() +finally: + del _test_cell_ +_ # This will be added to global scope +### END ``` 'a' is not polluting global scope @@ -99,6 +163,22 @@ a = "'a' is not polluting global scope" (a, True) # this is a comment on last line +``` + +``` python + +### BEGIN +def _test_cell_(): + #| echo: false + a = "'a' is not polluting global scope" + return (a, + True) # %%testcell +try: + _ = _test_cell_() +finally: + del _test_cell_ +_ # This will be added to global scope +### END ``` ("'a' is not polluting global scope", True) @@ -221,10 +301,38 @@ aaa # global variable print(aaa) # global variable bbb=123 def ccc(): return 567 +``` + +``` python + +### BEGIN +def _test_cell_(): + global bbb + global ccc + #| echo: false + # Input->Output and debug option + + print(aaa) # global variable + bbb=123 + def ccc(): return 567 +try: + _ = _test_cell_() +finally: + del _test_cell_ +if _ is not None: display(_) +### END ``` global variable +``` python + +### GLOBALS UPDATE CODE: +global bbb; bbb=locals()["bbb"] +global ccc; ccc=locals()["ccc"] +### +``` + ``` python # Variable bbb has been created in this context assert bbb==123 @@ -256,9 +364,9 @@ del aaa - DOCUMENTATION: - PYPI: - DETAILED DEMO: - \[https://github.com/artste/testcell/blob/main/demo/testcell_demo.ipynb\] -- LAUNCHING BLOG: - \[https://artste.github.io/blog/posts/introducing-testcell\] + +- LAUNCHING BLOG: [Introducing + `%%testcell`](https://artste.github.io/blog/posts/introducing-testcell) - COLAB DEMO: [testcell_demo.ipynb](https://colab.research.google.com/github/artste/testcell/blob/main/demo/testcell_demo.ipynb) - KAGGLE SAMPLE NOTEBOOK: diff --git a/nbs/index.ipynb b/nbs/index.ipynb index 4ce81dc..2d3c88c 100644 --- a/nbs/index.ipynb +++ b/nbs/index.ipynb @@ -61,7 +61,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "just import it with `import testcell` and then use the `%%testcell` cell magic." + "Just import it with `import testcell` and then use the `%%testcell` cell magic." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "import testcell\n", + "```" ] }, { @@ -134,107 +143,21 @@ "outputs": [ { "data": { - "text/html": [ - "
### BEGIN\n",
-       "def _test_cell_():\n",
-       "\t#| echo: false\n",
-       "\ta = "'a' is not polluting global scope"\n",
-       "\treturn a # %%testcell\n",
-       "try:\n",
-       "\t_ = _test_cell_()\n",
-       "finally:\n",
-       "\tdel _test_cell_\n",
-       "_ # This will be added to global scope\n",
-       "### END\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", - "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", - "\t\\PY{c+c1}{\\PYZsh{}| echo: false}\n", - "\t\\PY{n}{a} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{a}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{ is not polluting global scope}\\PY{l+s+s2}{\\PYZdq{}}\n", - "\t\\PY{k}{return} \\PY{n}{a} \\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell}\n", - "\\PY{k}{try}\\PY{p}{:}\n", - "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", - "\\PY{k}{finally}\\PY{p}{:}\n", - "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", - "\\PY{n}{\\PYZus{}} \\PY{c+c1}{\\PYZsh{} This will be added to global scope}\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", - "\\end{Verbatim}\n" + "text/markdown": [ + "```python\n", + "\n", + "### BEGIN\n", + "def _test_cell_():\n", + "\t#| echo: false\n", + "\ta = \"'a' is not polluting global scope\"\n", + "\treturn a # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "_ # This will be added to global scope\n", + "### END\n", + "```" ], "text/plain": [ "\n", @@ -297,107 +220,21 @@ "outputs": [ { "data": { - "text/html": [ - "
### BEGIN\n",
-       "def _test_cell_():\n",
-       "\t#| echo: false\n",
-       "\ta = "'a' is not polluting global scope"\n",
-       "\treturn a # %%testcell\n",
-       "try:\n",
-       "\t_ = _test_cell_()\n",
-       "finally:\n",
-       "\tdel _test_cell_\n",
-       "if _ is not None: display(_)\n",
-       "### END\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", - "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", - "\t\\PY{c+c1}{\\PYZsh{}| echo: false}\n", - "\t\\PY{n}{a} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{a}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{ is not polluting global scope}\\PY{l+s+s2}{\\PYZdq{}}\n", - "\t\\PY{k}{return} \\PY{n}{a} \\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell}\n", - "\\PY{k}{try}\\PY{p}{:}\n", - "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", - "\\PY{k}{finally}\\PY{p}{:}\n", - "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", - "\\PY{k}{if} \\PY{n}{\\PYZus{}} \\PY{o+ow}{is} \\PY{o+ow}{not} \\PY{k+kc}{None}\\PY{p}{:} \\PY{n}{display}\\PY{p}{(}\\PY{n}{\\PYZus{}}\\PY{p}{)}\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", - "\\end{Verbatim}\n" + "text/markdown": [ + "```python\n", + "\n", + "### BEGIN\n", + "def _test_cell_():\n", + "\t#| echo: false\n", + "\ta = \"'a' is not polluting global scope\"\n", + "\treturn a # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "if _ is not None: display(_)\n", + "### END\n", + "```" ], "text/plain": [ "\n", @@ -450,107 +287,21 @@ "outputs": [ { "data": { - "text/html": [ - "
### BEGIN\n",
-       "def _test_cell_():\n",
-       "\t#| echo: false\n",
-       "\ta = "'a' is not polluting global scope"\n",
-       "\ta;\n",
-       "try:\n",
-       "\t_ = _test_cell_()\n",
-       "finally:\n",
-       "\tdel _test_cell_\n",
-       "_ # This will be added to global scope\n",
-       "### END\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", - "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", - "\t\\PY{c+c1}{\\PYZsh{}| echo: false}\n", - "\t\\PY{n}{a} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{a}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{ is not polluting global scope}\\PY{l+s+s2}{\\PYZdq{}}\n", - "\t\\PY{n}{a}\\PY{p}{;}\n", - "\\PY{k}{try}\\PY{p}{:}\n", - "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", - "\\PY{k}{finally}\\PY{p}{:}\n", - "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", - "\\PY{n}{\\PYZus{}} \\PY{c+c1}{\\PYZsh{} This will be added to global scope}\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", - "\\end{Verbatim}\n" + "text/markdown": [ + "```python\n", + "\n", + "### BEGIN\n", + "def _test_cell_():\n", + "\t#| echo: false\n", + "\ta = \"'a' is not polluting global scope\"\n", + "\ta;\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "_ # This will be added to global scope\n", + "### END\n", + "```" ], "text/plain": [ "\n", @@ -603,107 +354,21 @@ "outputs": [ { "data": { - "text/html": [ - "
### BEGIN\n",
-       "def _test_cell_():\n",
-       "\t#| echo: false\n",
-       "\ta = "'a' is not polluting global scope"\n",
-       "\treturn print(a) # %%testcell\n",
-       "try:\n",
-       "\t_ = _test_cell_()\n",
-       "finally:\n",
-       "\tdel _test_cell_\n",
-       "_ # This will be added to global scope\n",
-       "### END\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", - "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", - "\t\\PY{c+c1}{\\PYZsh{}| echo: false}\n", - "\t\\PY{n}{a} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{a}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{ is not polluting global scope}\\PY{l+s+s2}{\\PYZdq{}}\n", - "\t\\PY{k}{return} \\PY{n+nb}{print}\\PY{p}{(}\\PY{n}{a}\\PY{p}{)} \\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell}\n", - "\\PY{k}{try}\\PY{p}{:}\n", - "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", - "\\PY{k}{finally}\\PY{p}{:}\n", - "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", - "\\PY{n}{\\PYZus{}} \\PY{c+c1}{\\PYZsh{} This will be added to global scope}\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", - "\\end{Verbatim}\n" + "text/markdown": [ + "```python\n", + "\n", + "### BEGIN\n", + "def _test_cell_():\n", + "\t#| echo: false\n", + "\ta = \"'a' is not polluting global scope\"\n", + "\treturn print(a) # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "_ # This will be added to global scope\n", + "### END\n", + "```" ], "text/plain": [ "\n", @@ -765,109 +430,22 @@ "outputs": [ { "data": { - "text/html": [ - "
### BEGIN\n",
-       "def _test_cell_():\n",
-       "\t#| echo: false\n",
-       "\ta = "'a' is not polluting global scope"\n",
-       "\treturn (a,\n",
-       "\t True) # %%testcell\n",
-       "try:\n",
-       "\t_ = _test_cell_()\n",
-       "finally:\n",
-       "\tdel _test_cell_\n",
-       "_ # This will be added to global scope\n",
-       "### END\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", - "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", - "\t\\PY{c+c1}{\\PYZsh{}| echo: false}\n", - "\t\\PY{n}{a} \\PY{o}{=} \\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{a}\\PY{l+s+s2}{\\PYZsq{}}\\PY{l+s+s2}{ is not polluting global scope}\\PY{l+s+s2}{\\PYZdq{}}\n", - "\t\\PY{k}{return} \\PY{p}{(}\\PY{n}{a}\\PY{p}{,}\n", - "\t \\PY{k+kc}{True}\\PY{p}{)} \\PY{c+c1}{\\PYZsh{} \\PYZpc{}\\PYZpc{}testcell}\n", - "\\PY{k}{try}\\PY{p}{:}\n", - "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", - "\\PY{k}{finally}\\PY{p}{:}\n", - "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", - "\\PY{n}{\\PYZus{}} \\PY{c+c1}{\\PYZsh{} This will be added to global scope}\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", - "\\end{Verbatim}\n" + "text/markdown": [ + "```python\n", + "\n", + "### BEGIN\n", + "def _test_cell_():\n", + "\t#| echo: false\n", + "\ta = \"'a' is not polluting global scope\"\n", + "\treturn (a,\n", + "\t True) # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "_ # This will be added to global scope\n", + "### END\n", + "```" ], "text/plain": [ "\n", @@ -1307,117 +885,26 @@ "outputs": [ { "data": { - "text/html": [ - "
### BEGIN\n",
-       "def _test_cell_():\n",
-       "\tglobal bbb\n",
-       "\tglobal ccc\n",
-       "\t#| echo: false\n",
-       "\t# Input->Output and debug option\n",
-       "\t\n",
-       "\tprint(aaa) # global variable\n",
-       "\tbbb=123\n",
-       "\tdef ccc(): return 567\n",
-       "try:\n",
-       "\t_ = _test_cell_()\n",
-       "finally:\n",
-       "\tdel _test_cell_\n",
-       "if _ is not None: display(_)\n",
-       "### END\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} BEGIN}\n", - "\\PY{k}{def} \\PY{n+nf}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:}\n", - "\t\\PY{k}{global} \\PY{n}{bbb}\n", - "\t\\PY{k}{global} \\PY{n}{ccc}\n", - "\t\\PY{c+c1}{\\PYZsh{}| echo: false}\n", - "\t\\PY{c+c1}{\\PYZsh{} Input\\PYZhy{}\\PYZgt{}Output and debug option}\n", + "text/markdown": [ + "```python\n", + "\n", + "### BEGIN\n", + "def _test_cell_():\n", + "\tglobal bbb\n", + "\tglobal ccc\n", + "\t#| echo: false\n", + "\t# Input->Output and debug option\n", "\t\n", - "\t\\PY{n+nb}{print}\\PY{p}{(}\\PY{n}{aaa}\\PY{p}{)} \\PY{c+c1}{\\PYZsh{} global variable}\n", - "\t\\PY{n}{bbb}\\PY{o}{=}\\PY{l+m+mi}{123}\n", - "\t\\PY{k}{def} \\PY{n+nf}{ccc}\\PY{p}{(}\\PY{p}{)}\\PY{p}{:} \\PY{k}{return} \\PY{l+m+mi}{567}\n", - "\\PY{k}{try}\\PY{p}{:}\n", - "\t\\PY{n}{\\PYZus{}} \\PY{o}{=} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\\PY{p}{(}\\PY{p}{)}\n", - "\\PY{k}{finally}\\PY{p}{:}\n", - "\t\\PY{k}{del} \\PY{n}{\\PYZus{}test\\PYZus{}cell\\PYZus{}}\n", - "\\PY{k}{if} \\PY{n}{\\PYZus{}} \\PY{o+ow}{is} \\PY{o+ow}{not} \\PY{k+kc}{None}\\PY{p}{:} \\PY{n}{display}\\PY{p}{(}\\PY{n}{\\PYZus{}}\\PY{p}{)}\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} END}\n", - "\\end{Verbatim}\n" + "\tprint(aaa) # global variable\n", + "\tbbb=123\n", + "\tdef ccc(): return 567\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "if _ is not None: display(_)\n", + "### END\n", + "```" ], "text/plain": [ "\n", @@ -1451,93 +938,14 @@ }, { "data": { - "text/html": [ - "
### GLOBALS UPDATE CODE:\n",
-       "global bbb; bbb=locals()["bbb"]\n",
-       "global ccc; ccc=locals()["ccc"]\n",
-       "###\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{} GLOBALS UPDATE CODE:}\n", - "\\PY{k}{global} \\PY{n}{bbb}\\PY{p}{;} \\PY{n}{bbb}\\PY{o}{=}\\PY{n+nb}{locals}\\PY{p}{(}\\PY{p}{)}\\PY{p}{[}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{bbb}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{]}\n", - "\\PY{k}{global} \\PY{n}{ccc}\\PY{p}{;} \\PY{n}{ccc}\\PY{o}{=}\\PY{n+nb}{locals}\\PY{p}{(}\\PY{p}{)}\\PY{p}{[}\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{ccc}\\PY{l+s+s2}{\\PYZdq{}}\\PY{p}{]}\n", - "\\PY{c+c1}{\\PYZsh{}\\PYZsh{}\\PYZsh{}}\n", - "\\end{Verbatim}\n" + "text/markdown": [ + "```python\n", + "\n", + "### GLOBALS UPDATE CODE:\n", + "global bbb; bbb=locals()[\"bbb\"]\n", + "global ccc; ccc=locals()[\"ccc\"]\n", + "###\n", + "```" ], "text/plain": [ "\n", @@ -1636,10 +1044,10 @@ "+ PROJECT PAGE: [https://github.com/artste/testcell](https://github.com/artste/testcell)\n", "+ DOCUMENTATION: [https://artste.github.io/testcell](https://artste.github.io/testcell)\n", "+ PYPI: [https://pypi.org/project/testcell](https://pypi.org/project/testcell)\n", - "+ DETAILED DEMO: [https://github.com/artste/testcell/blob/main/demo/testcell_demo.ipynb]\n", - "+ LAUNCHING BLOG: [https://artste.github.io/blog/posts/introducing-testcell]\n", + "+ DETAILED DEMO: \n", + "+ LAUNCHING BLOG: [Introducing `%%testcell`](https://artste.github.io/blog/posts/introducing-testcell)\n", "+ COLAB DEMO: [testcell_demo.ipynb](https://colab.research.google.com/github/artste/testcell/blob/main/demo/testcell_demo.ipynb)\n", - "+ KAGGLE SAMPLE NOTEBOOK: [https://www.kaggle.com/artste/introducing-testcell](https://www.kaggle.com/artste/introducing-testcell)" + "+ KAGGLE SAMPLE NOTEBOOK: " ] }, { From 771e984421f33c274eeaa18397e161a847b4549a Mon Sep 17 00:00:00 2001 From: Stefano Giomo Date: Wed, 5 Nov 2025 09:54:43 +0000 Subject: [PATCH 07/18] Removed newline on Code blocks --- nbs/02_testcell.ipynb | 16 ++-------------- testcell/__init__.py | 4 ++-- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/nbs/02_testcell.ipynb b/nbs/02_testcell.ipynb index 1bca81f..9d2db37 100644 --- a/nbs/02_testcell.ipynb +++ b/nbs/02_testcell.ipynb @@ -489,7 +489,7 @@ " wrapped_cell = '\\n'.join(arr)\n", "\n", " if use_banner: display(noglobals_message_box if noglobals else testcell_message_box)\n", - " if verbose: display(Code('\\n### BEGIN\\n'+wrapped_cell+'\\n### END',language='python'))\n", + " if verbose: display(Code('### BEGIN\\n'+wrapped_cell+'\\n### END',language='python'))\n", "\n", " if not dryrun: exec(wrapped_cell,_globals,_locals)\n", " \n", @@ -497,7 +497,7 @@ " arr2 = []\n", " for o in outputs: arr2 += [f'global {o}; {o}=locals()[\"{o}\"]'] # this forwards objects to global scope\n", " globals_update_code = '\\n'.join(arr2)\n", - " if debug: display(Code('\\n### GLOBALS UPDATE CODE:\\n'+globals_update_code+'\\n###',language='python'))\n", + " if debug: display(Code('### GLOBALS UPDATE CODE:\\n'+globals_update_code+'\\n###',language='python'))\n", " exec(globals_update_code,local_ns,_globals)\n", " \n", " return None if noreturn else _globals.get('_',None) # this closes the loop of integration" @@ -989,7 +989,6 @@ "data": { "text/markdown": [ "```python\n", - "\n", "### BEGIN\n", "def _test_cell_():\n", "\t# %%testcell verbose\n", @@ -1004,7 +1003,6 @@ "```" ], "text/plain": [ - "\n", "### BEGIN\n", "def _test_cell_():\n", "\t# %%testcell verbose\n", @@ -1057,7 +1055,6 @@ "data": { "text/markdown": [ "```python\n", - "\n", "### BEGIN\n", "def _test_cell_():\n", "\t# %%testcell dryrun\n", @@ -1074,7 +1071,6 @@ "```" ], "text/plain": [ - "\n", "### BEGIN\n", "def _test_cell_():\n", "\t# %%testcell dryrun\n", @@ -1328,7 +1324,6 @@ "data": { "text/markdown": [ "```python\n", - "\n", "### BEGIN\n", "def _test_cell_():\n", "\t# %%testcell verbose\n", @@ -1342,7 +1337,6 @@ "```" ], "text/plain": [ - "\n", "### BEGIN\n", "def _test_cell_():\n", "\t# %%testcell verbose\n", @@ -1400,7 +1394,6 @@ "data": { "text/markdown": [ "```python\n", - "\n", "### BEGIN\n", "def _test_cell_():\n", "\t# %%testcelln\n", @@ -1414,7 +1407,6 @@ "```" ], "text/plain": [ - "\n", "### BEGIN\n", "def _test_cell_():\n", "\t# %%testcelln\n", @@ -1541,7 +1533,6 @@ "data": { "text/markdown": [ "```python\n", - "\n", "### BEGIN\n", "def _test_cell_():\n", "\tglobal kkk\n", @@ -1562,7 +1553,6 @@ "```" ], "text/plain": [ - "\n", "### BEGIN\n", "def _test_cell_():\n", "\tglobal kkk\n", @@ -1598,7 +1588,6 @@ "data": { "text/markdown": [ "```python\n", - "\n", "### GLOBALS UPDATE CODE:\n", "global kkk; kkk=locals()[\"kkk\"]\n", "global fff; fff=locals()[\"fff\"]\n", @@ -1606,7 +1595,6 @@ "```" ], "text/plain": [ - "\n", "### GLOBALS UPDATE CODE:\n", "global kkk; kkk=locals()[\"kkk\"]\n", "global fff; fff=locals()[\"fff\"]\n", diff --git a/testcell/__init__.py b/testcell/__init__.py index a693b46..507737a 100644 --- a/testcell/__init__.py +++ b/testcell/__init__.py @@ -137,7 +137,7 @@ def testcell(line, cell, local_ns): wrapped_cell = '\n'.join(arr) if use_banner: display(noglobals_message_box if noglobals else testcell_message_box) - if verbose: display(Code('\n### BEGIN\n'+wrapped_cell+'\n### END',language='python')) + if verbose: display(Code('### BEGIN\n'+wrapped_cell+'\n### END',language='python')) if not dryrun: exec(wrapped_cell,_globals,_locals) @@ -145,7 +145,7 @@ def testcell(line, cell, local_ns): arr2 = [] for o in outputs: arr2 += [f'global {o}; {o}=locals()["{o}"]'] # this forwards objects to global scope globals_update_code = '\n'.join(arr2) - if debug: display(Code('\n### GLOBALS UPDATE CODE:\n'+globals_update_code+'\n###',language='python')) + if debug: display(Code('### GLOBALS UPDATE CODE:\n'+globals_update_code+'\n###',language='python')) exec(globals_update_code,local_ns,_globals) return None if noreturn else _globals.get('_',None) # this closes the loop of integration From 6784d5077d9aecdc12374b2c8928a5b53738c519 Mon Sep 17 00:00:00 2001 From: Stefano Giomo Date: Thu, 6 Nov 2025 10:41:45 +0000 Subject: [PATCH 08/18] Improved readme --- README.md | 349 ++++++------------ nbs/index.ipynb | 956 ++++++++++-------------------------------------- 2 files changed, 322 insertions(+), 983 deletions(-) diff --git a/README.md b/README.md index 31b57b6..eaec6d4 100644 --- a/README.md +++ b/README.md @@ -3,73 +3,70 @@ -**TL;DR**: `%%testcell` prevents your testing cells from affecting the -global namespace. - -The Python cell magic `%%testcell` executes a cell without *polluting* -the notebook’s global namespace. This is useful whenever you want to -test your code without having any of the local variables escape that -cell. - -What’s happening under the hood is that your cell code, before being -executed, is wrapped in a temporary function that will be deleted after -execution. To give you the feeling of *seamless integration* the last -statement is optionally returned like it happens in a normal cell. +**`%%testcell`** executes notebook cells in isolation: no symbols leak +into your global namespace. Test code, try snippets, and experiment +freely: no variables, functions, classes, or imports left behind. +You explore, **`%%testcell`** keeps your namespace clean. **WARNING:** this doesn’t protect you from *the side effects of your code* like deleting a file or mutating the state of a global variable. [![](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/artste/testcell/blob/main/demo/testcell_demo.ipynb) - [![](https://kaggle.com/static/images/open-in-kaggle.svg)](https://www.kaggle.com/artste/introducing-testcell) ## Install +Lightweight and reliable: `%%testcell` depends only on IPython and works +in all major notebook environments including Jupyter, Colab, Kaggle, +Modal, Solveit, and the IPython console. + ``` sh pip install testcell ``` -## How to use +## Quick Start -Just import it with `import testcell` and then use the `%%testcell` cell -magic. +First import `testcell`: ``` python import testcell ``` +Then use it: + ``` python %%testcell -a = "'a' is not polluting global scope" -a +temp_var = "This won't pollute namespace" +temp_var ``` - "'a' is not polluting global scope" + "This won't pollute namespace" ``` python -assert 'a' not in locals() +# temp_var doesn't exist — it was only defined inside the test cell +temp_var # NameError: name 'temp_var' is not defined ``` -What is happening under the hood is that `%%testcell` wraps your cell’s -code with a function, execute it and then deletes it. Adding the -`verbose` keyword will print which code will be executed. +## How it works + +Import `testcell` and use the `%%testcell` magic at the top of any cell. +Under the hood, your code is wrapped in a temporary function that +executes and then deletes itself. -NOTE: The actual cell code is enclosed within `BEGIN` and `END` comment -blocks for improved readability. +Use `verbose` to see the generated wrapper code: ``` python %%testcell verbose -a = "'a' is not polluting global scope" -a +result = "isolated execution" +result ``` ``` python - ### BEGIN def _test_cell_(): #| echo: false - a = "'a' is not polluting global scope" - return a # %%testcell + result = "isolated execution" + return result # %%testcell try: _ = _test_cell_() finally: @@ -78,285 +75,175 @@ _ # This will be added to global scope ### END ``` - "'a' is not polluting global scope" + 'isolated execution' -If you’re just interested in seeing what will be executed, but actually -not executing it, you can use `dryrun` option: +## Suppressing output -``` python -%%testcell dryrun -a = "'a' is not polluting global scope" -a -``` +Like normal Jupyter cells, add a semicolon `;` to the last statement to +suppress display: ``` python - -### BEGIN -def _test_cell_(): - #| echo: false - a = "'a' is not polluting global scope" - return a # %%testcell -try: - _ = _test_cell_() -finally: - del _test_cell_ -if _ is not None: display(_) -### END +%%testcell +calculation = 42 * 2 +calculation; ``` -If you add a semicolon `;` at the end of your last statement no `return` -statement is added and nothing is displayed like a normal jupyter cell. +No output is displayed, and `calculation` still doesn’t leak to globals. -``` python -%%testcell verbose -a = "'a' is not polluting global scope" -a; -``` +## Skip execution -``` python +Skip cells without deleting code using the `skip` command. -### BEGIN -def _test_cell_(): - #| echo: false - a = "'a' is not polluting global scope" - a; -try: - _ = _test_cell_() -finally: - del _test_cell_ -_ # This will be added to global scope -### END -``` - -`testcell` works seamlessly with existing `print` or `display` -statements on last line: +**IMPORTANT**: This is especially useful in notebook environments like +**Colab, Kaggle, and Modal** where you can’t use Jupyter’s “Raw cell” +type to disable execution. ``` python -%%testcell verbose -a = "'a' is not polluting global scope" -print(a) +%%testcell skip +raise ValueError('This will not execute') ``` -``` python + ℹ️ This cell has been skipped -### BEGIN -def _test_cell_(): - #| echo: false - a = "'a' is not polluting global scope" - return print(a) # %%testcell -try: - _ = _test_cell_() -finally: - del _test_cell_ -_ # This will be added to global scope -### END -``` +To skip all `%%testcell` cells at once (useful for production runs), +use: `testcell.global_skip = True` - 'a' is not polluting global scope +## Visual marking -Moreover, thanks to `ast`, it properly deals with complex situations -like comments on the last line and multi lines statements +Use `banner` to display a colored indicator at the top of cell output, +making test cells instantly recognizable: ``` python -%%testcell verbose -a = "'a' is not polluting global scope" -(a, - True) -# this is a comment on last line +%%testcell banner +"clearly marked" ``` -``` python - -### BEGIN -def _test_cell_(): - #| echo: false - a = "'a' is not polluting global scope" - return (a, - True) # %%testcell -try: - _ = _test_cell_() -finally: - del _test_cell_ -_ # This will be added to global scope -### END -``` + 🟡 testcell - ("'a' is not polluting global scope", True) + 'clearly marked' -### Skip execution +**The banner adapts to your environment.** In HTML-based notebooks like +Jupyter, it displays as a full-width colored box. In console +environments like IPython, it appears as text with an emoji. -It is possible to skip a cell execution using `skip` command. This is -useful when you want to keep around the code but don’t actually run it. -It’s also possible to skip **all cells marked with `%%testcell`** using -the following syntax: `testcell.global_skip=True`. +Colors and emojis are fully customizable through `testcell.params`. -``` python -%%testcell skip -raise ValueError('This should not be executed') -``` +**IMPORTANT**: To enable banners for all `%%testcell` cells, use: +**`testcell.global_use_banner = True`** - ℹ️ This cell has been skipped +## Run in complete isolation -### Run in isolation +`%%testcelln` is a shortcut for `%%testcell noglobals` and executes +cells with **zero access** to your notebook’s global scope. Only +Python’s `__builtins__` are available. -`%%testcelln` is a shortcut for `%%testcell noglobals` and executes the -cell in complete isolation from the global scope. This is very useful -when you want to ensure that global variables or namespaces are not -accessible within the cell. +This is powerful for: - **Detecting hidden dependencies**: catch when +your code accidentally relies on global variables - **Testing +portability**: verify functions work standalone - **Clean slate +execution**: run code exactly as it would in a fresh Python session ``` python -aaa = 'global variable' +my_global = "I'm in the global scope" ``` ``` python %%testcell -'aaa' in globals() +'my_global' in globals() ``` - True - ``` python -%%testcell noglobals -'aaa' in globals() + True # my_global is available ``` - False - ``` python %%testcelln -'aaa' in globals() +'my_global' in globals() ``` - False +``` python + False # my_global is NOT available +``` ``` python %%testcelln globals().keys() ``` +``` python dict_keys(['__builtins__']) +``` -With `%%testcelln` inside the cell, you’ll be able to access only -`__builtins__` (aka: standard python’s functions). **It behaves like a -notebook-in-notebook**. - -``` python -%%testcell -def my_function(x): - print(aaa) # global variable - return x +## Explicit dependencies -try: - my_function(123) -except Exception as e: - print(e) -``` +The `(inputs)->(outputs)` syntax gives you precise control: you can pass +any symbol (variables, functions, classes) into the isolated context and +save only chosen ones back to globals. - global variable +This **forces explicit dependency declaration**, giving you full control +over what enters and exits the cell. It prevents accidental reliance on +symbols from the main context that would hurt you when exporting the +code. ``` python -%%testcelln -def my_function(x): - print(aaa) # global variable - return x - -try: - my_function(123) -except Exception as e: - print(e) +data = [1, 2, 3, 4, 5] ``` - 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 have been -created outside of this cell (aka: if you have side effects) this will -not protect you. - ``` python -aaa +%%testcelln (data)->(calculate_stats) +# Only 'data' is available, only 'calculate_stats' is saved + +def calculate_stats(values): + return { + 'mean': sum(values) / len(values), + 'min': min(values), + 'max': max(values) + } + +# Test it works +print(calculate_stats(data)) ``` - 'global variable' + {'mean': 3.0, 'min': 1, 'max': 5} -There is a dedicated syntax to enable passing variables to an isolated -context and saving variables of objects back to the main context with -the `(inputs)->(outputs)` syntax +`calculate_stats` now **exists in globals**. No test code or +intermediate variables leaked. ``` python -%%testcelln (aaa) -# Input only -aaa # global variable +calculate_stats([10, 20, 30]) ``` - 'global variable' + {'mean': 20.0, 'min': 10, 'max': 30} -``` python -%%testcelln (aaa)->(bbb,ccc) debug -# Input->Output and debug option +## Advanced parsing -print(aaa) # global variable -bbb=123 -def ccc(): return 567 -``` +Thanks to Python’s `ast` module, `%%testcell` correctly handles complex +code patterns including comments on the last line and multi-line +statements: ``` python +%%testcell verbose +result = "complex parsing" +(result, + True) +# comment on last line +``` +``` python ### BEGIN def _test_cell_(): - global bbb - global ccc #| echo: false - # Input->Output and debug option - - print(aaa) # global variable - bbb=123 - def ccc(): return 567 + result = "complex parsing" + return (result, + True) # %%testcell try: _ = _test_cell_() finally: del _test_cell_ -if _ is not None: display(_) +_ # This will be added to global scope ### END ``` - global variable - -``` python - -### GLOBALS UPDATE CODE: -global bbb; bbb=locals()["bbb"] -global ccc; ccc=locals()["ccc"] -### -``` - -``` python -# Variable bbb has been created in this context -assert bbb==123 -assert ccc()==567 - -# cleaup -del bbb,ccc -``` - -``` python -%%testcell -# WARNING: this will alter the state of global variable: -globals().update({'aaa' : 'modified global variable'}); -``` - -``` python -aaa -``` - - 'modified global variable' - -``` python -del aaa -``` + ('complex parsing', True) ## Links: @@ -365,6 +252,8 @@ del aaa - PYPI: - DETAILED DEMO: +- USE CASE ZOO: + - LAUNCHING BLOG: [Introducing `%%testcell`](https://artste.github.io/blog/posts/introducing-testcell) - COLAB DEMO: diff --git a/nbs/index.ipynb b/nbs/index.ipynb index 2d3c88c..2d4b20b 100644 --- a/nbs/index.ipynb +++ b/nbs/index.ipynb @@ -21,30 +21,22 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**TL;DR**: `%%testcell` prevents your testing cells from affecting the global namespace.\n", - "\n", - "The Python cell magic `%%testcell` executes a cell without *polluting* the notebook's global namespace. This is useful whenever you want to test your code without having any of the local variables escape that cell.\n", - "\n", - "What's happening under the hood is that your cell code, before being executed, is wrapped in a temporary function that will be deleted after execution. To give you the feeling of *seamless integration* the last statement is optionally returned like it happens in a normal cell.\n", + "**`%%testcell`** executes notebook cells in isolation: no symbols leak into your global namespace.\n", + "Test code, try snippets, and experiment freely: no variables, functions, classes, or imports left behind. \n", + "You explore, **`%%testcell`** keeps your namespace clean.\n", "\n", "**WARNING:** this doesn't protect you from *the side effects of your code* like deleting a file or mutating the state of a global variable.\n", "\n", - "[![](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/artste/testcell/blob/main/demo/testcell_demo.ipynb) \n", - "\n", - "[![](https://kaggle.com/static/images/open-in-kaggle.svg)](https://www.kaggle.com/artste/introducing-testcell)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Install" + "[![](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/artste/testcell/blob/main/demo/testcell_demo.ipynb) [![](https://kaggle.com/static/images/open-in-kaggle.svg)](https://www.kaggle.com/artste/introducing-testcell)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## Install\n", + "Lightweight and reliable: `%%testcell` depends only on IPython and works in all major notebook environments including Jupyter, Colab, Kaggle, Modal, Solveit, and the IPython console.\n", + "\n", "```sh\n", "pip install testcell\n", "```" @@ -54,33 +46,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## How to use" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Just import it with `import testcell` and then use the `%%testcell` cell magic." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "## Quick Start\n", + "\n", + "First import `testcell`:\n", "```python\n", "import testcell\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "```\n", + "\n", + "Then use it:\n", "```python\n", "%%testcell\n", - "a = \"'a' is not polluting global scope\"\n", - "a\n", + "temp_var = \"This won't pollute namespace\"\n", + "temp_var\n", "```" ] }, @@ -92,7 +69,7 @@ { "data": { "text/plain": [ - "\"'a' is not polluting global scope\"" + "\"This won't pollute namespace\"" ] }, "execution_count": null, @@ -103,36 +80,34 @@ "source": [ "%%testcell\n", "#| echo: false\n", - "a = \"'a' is not polluting global scope\"\n", - "a" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "assert 'a' not in locals()" + "temp_var = \"This won't pollute namespace\"\n", + "temp_var" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "What is happening under the hood is that `%%testcell` wraps your cell's code with a function, execute it and then deletes it. Adding the `verbose` keyword will print which code will be executed.\n", - "\n", - "NOTE: The actual cell code is enclosed within `BEGIN` and `END` comment blocks for improved readability." + "```python\n", + "# temp_var doesn't exist — it was only defined inside the test cell\n", + "temp_var # NameError: name 'temp_var' is not defined\n", + "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## How it works\n", + "\n", + "Import `testcell` and use the `%%testcell` magic at the top of any cell. Under the hood, your code is wrapped in a temporary function that executes and then deletes itself.\n", + "\n", + "Use `verbose` to see the generated wrapper code:\n", + "\n", "```python\n", "%%testcell verbose\n", - "a = \"'a' is not polluting global scope\"\n", - "a\n", + "result = \"isolated execution\"\n", + "result\n", "```" ] }, @@ -145,12 +120,11 @@ "data": { "text/markdown": [ "```python\n", - "\n", "### BEGIN\n", "def _test_cell_():\n", "\t#| echo: false\n", - "\ta = \"'a' is not polluting global scope\"\n", - "\treturn a # %%testcell\n", + "\tresult = \"isolated execution\"\n", + "\treturn result # %%testcell\n", "try:\n", "\t_ = _test_cell_()\n", "finally:\n", @@ -160,12 +134,11 @@ "```" ], "text/plain": [ - "\n", "### BEGIN\n", "def _test_cell_():\n", "\t#| echo: false\n", - "\ta = \"'a' is not polluting global scope\"\n", - "\treturn a # %%testcell\n", + "\tresult = \"isolated execution\"\n", + "\treturn result # %%testcell\n", "try:\n", "\t_ = _test_cell_()\n", "finally:\n", @@ -180,7 +153,7 @@ { "data": { "text/plain": [ - "\"'a' is not polluting global scope\"" + "'isolated execution'" ] }, "execution_count": null, @@ -191,25 +164,22 @@ "source": [ "%%testcell verbose\n", "#| echo: false\n", - "a = \"'a' is not polluting global scope\"\n", - "a" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you're just interested in seeing what will be executed, but actually not executing it, you can use `dryrun` option:" + "result = \"isolated execution\"\n", + "result" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## Suppressing output\n", + "\n", + "Like normal Jupyter cells, add a semicolon `;` to the last statement to suppress display:\n", + "\n", "```python\n", - "%%testcell dryrun\n", - "a = \"'a' is not polluting global scope\"\n", - "a\n", + "%%testcell\n", + "calculation = 42 * 2\n", + "calculation;\n", "```" ] }, @@ -217,66 +187,34 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "```python\n", - "\n", - "### BEGIN\n", - "def _test_cell_():\n", - "\t#| echo: false\n", - "\ta = \"'a' is not polluting global scope\"\n", - "\treturn a # %%testcell\n", - "try:\n", - "\t_ = _test_cell_()\n", - "finally:\n", - "\tdel _test_cell_\n", - "if _ is not None: display(_)\n", - "### END\n", - "```" - ], - "text/plain": [ - "\n", - "### BEGIN\n", - "def _test_cell_():\n", - "\t#| echo: false\n", - "\ta = \"'a' is not polluting global scope\"\n", - "\treturn a # %%testcell\n", - "try:\n", - "\t_ = _test_cell_()\n", - "finally:\n", - "\tdel _test_cell_\n", - "if _ is not None: display(_)\n", - "### END" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "%%testcell dryrun\n", + "%%testcell\n", "#| echo: false\n", - "a = \"'a' is not polluting global scope\"\n", - "a" + "calculation = 42 * 2\n", + "calculation;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "If you add a semicolon `;` at the end of your last statement no `return` statement is added and nothing is displayed like a normal jupyter cell." + "No output is displayed, and `calculation` still doesn't leak to globals." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## Skip execution\n", + "\n", + "Skip cells without deleting code using the `skip` command.\n", + "\n", + "**IMPORTANT**: This is especially useful in notebook environments like **Colab, Kaggle, and Modal** where you can't use Jupyter's \"Raw cell\" type to disable execution.\n", + "\n", "```python\n", - "%%testcell verbose\n", - "a = \"'a' is not polluting global scope\"\n", - "a;\n", + "%%testcell skip\n", + "raise ValueError('This will not execute')\n", "```" ] }, @@ -287,35 +225,15 @@ "outputs": [ { "data": { - "text/markdown": [ - "```python\n", + "text/html": [ "\n", - "### BEGIN\n", - "def _test_cell_():\n", - "\t#| echo: false\n", - "\ta = \"'a' is not polluting global scope\"\n", - "\ta;\n", - "try:\n", - "\t_ = _test_cell_()\n", - "finally:\n", - "\tdel _test_cell_\n", - "_ # This will be added to global scope\n", - "### END\n", - "```" + "
\n", + " This cell has been skipped\n", + "
\n", + " " ], "text/plain": [ - "\n", - "### BEGIN\n", - "def _test_cell_():\n", - "\t#| echo: false\n", - "\ta = \"'a' is not polluting global scope\"\n", - "\ta;\n", - "try:\n", - "\t_ = _test_cell_()\n", - "finally:\n", - "\tdel _test_cell_\n", - "_ # This will be added to global scope\n", - "### END" + "ℹ️ This cell has been skipped" ] }, "metadata": {}, @@ -323,27 +241,49 @@ } ], "source": [ - "%%testcell verbose\n", + "%%testcell skip\n", + "#| echo: false\n", + "#| output: false\n", + "# This is here for testing purpose\n", + "raise ValueError('This should not be executed')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ℹ️ This cell has been skipped\n" + ] + } + ], + "source": [ "#| echo: false\n", - "a = \"'a' is not polluting global scope\"\n", - "a;" + "print(testcell.skip_message_box.__repr__())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "`testcell` works seamlessly with existing `print` or `display` statements on last line:" + "To skip all `%%testcell` cells at once (useful for production runs), use: `testcell.global_skip = True`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## Visual marking\n", + "\n", + "Use `banner` to display a colored indicator at the top of cell output, making test cells instantly recognizable:\n", + "\n", "```python\n", - "%%testcell verbose\n", - "a = \"'a' is not polluting global scope\"\n", - "print(a)\n", + "%%testcell banner\n", + "\"clearly marked\"\n", "```" ] }, @@ -352,74 +292,134 @@ "execution_count": null, "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "🟡 testcell\n" + ] + }, { "data": { - "text/markdown": [ - "```python\n", - "\n", - "### BEGIN\n", - "def _test_cell_():\n", - "\t#| echo: false\n", - "\ta = \"'a' is not polluting global scope\"\n", - "\treturn print(a) # %%testcell\n", - "try:\n", - "\t_ = _test_cell_()\n", - "finally:\n", - "\tdel _test_cell_\n", - "_ # This will be added to global scope\n", - "### END\n", - "```" - ], "text/plain": [ - "\n", - "### BEGIN\n", - "def _test_cell_():\n", - "\t#| echo: false\n", - "\ta = \"'a' is not polluting global scope\"\n", - "\treturn print(a) # %%testcell\n", - "try:\n", - "\t_ = _test_cell_()\n", - "finally:\n", - "\tdel _test_cell_\n", - "_ # This will be added to global scope\n", - "### END" + "'clearly marked'" ] }, "metadata": {}, "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "'a' is not polluting global scope\n" - ] } ], "source": [ - "%%testcell verbose\n", "#| echo: false\n", - "a = \"'a' is not polluting global scope\"\n", - "print(a)" + "print(testcell.testcell_message_box.__repr__())\n", + "display('clearly marked')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**The banner adapts to your environment.** In HTML-based notebooks like Jupyter, it displays as a full-width colored box. In console environments like IPython, it appears as text with an emoji.\n", + "\n", + "Colors and emojis are fully customizable through `testcell.params`.\n", + "\n", + "**IMPORTANT**: To enable banners for all `%%testcell` cells, use: **`testcell.global_use_banner = True`**" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Run in complete isolation\n", + "\n", + "`%%testcelln` is a shortcut for `%%testcell noglobals` and executes cells with **zero access** to your notebook's global scope. Only Python's `__builtins__` are available.\n", + "\n", + "This is powerful for:\n", + "- **Detecting hidden dependencies**: catch when your code accidentally relies on global variables\n", + "- **Testing portability**: verify functions work standalone\n", + "- **Clean slate execution**: run code exactly as it would in a fresh Python session\n", + "\n", + "```python\n", + "my_global = \"I'm in the global scope\"\n", + "```\n", + "\n", + "```python\n", + "%%testcell\n", + "'my_global' in globals()\n", + "```\n", + "```python\n", + " True # my_global is available\n", + "```\n", + "```python\n", + "%%testcelln\n", + "'my_global' in globals()\n", + "```\n", + "```python\n", + " False # my_global is NOT available\n", + "```\n", + "```python\n", + "%%testcelln\n", + "globals().keys()\n", + "```\n", + "```python\n", + " dict_keys(['__builtins__'])\n", + "````" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Moreover, thanks to `ast`, it properly deals with complex situations like comments on the last line and multi lines statements" + "## Explicit dependencies\n", + "\n", + "The `(inputs)->(outputs)` syntax gives you precise control: you can pass any symbol (variables, functions, classes) into the isolated context and save only chosen ones back to globals.\n", + "\n", + "This **forces explicit dependency declaration**, giving you full control over what enters and exits the cell. It prevents accidental reliance on symbols from the main context that would hurt you when exporting the code.\n", + "\n", + "```python\n", + "data = [1, 2, 3, 4, 5]\n", + "```\n", + "\n", + "```python\n", + "%%testcelln (data)->(calculate_stats)\n", + "# Only 'data' is available, only 'calculate_stats' is saved\n", + "\n", + "def calculate_stats(values):\n", + " return {\n", + " 'mean': sum(values) / len(values),\n", + " 'min': min(values),\n", + " 'max': max(values)\n", + " }\n", + "\n", + "# Test it works\n", + "print(calculate_stats(data))\n", + "```\n", + "\n", + " {'mean': 3.0, 'min': 1, 'max': 5}\n", + "\n", + "`calculate_stats` now **exists in globals**. No test code or intermediate variables leaked.\n", + "\n", + "```python\n", + "calculate_stats([10, 20, 30])\n", + "```\n", + "\n", + " {'mean': 20.0, 'min': 10, 'max': 30}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## Advanced parsing\n", + "\n", + "Thanks to Python's `ast` module, `%%testcell` correctly handles complex code patterns including comments on the last line and multi-line statements:\n", + "\n", "```python\n", "%%testcell verbose\n", - "a = \"'a' is not polluting global scope\"\n", - "(a,\n", + "result = \"complex parsing\"\n", + "(result,\n", " True)\n", - "# this is a comment on last line\n", + "# comment on last line\n", "```" ] }, @@ -432,12 +432,11 @@ "data": { "text/markdown": [ "```python\n", - "\n", "### BEGIN\n", "def _test_cell_():\n", "\t#| echo: false\n", - "\ta = \"'a' is not polluting global scope\"\n", - "\treturn (a,\n", + "\tresult = \"complex parsing\"\n", + "\treturn (result,\n", "\t True) # %%testcell\n", "try:\n", "\t_ = _test_cell_()\n", @@ -448,12 +447,11 @@ "```" ], "text/plain": [ - "\n", "### BEGIN\n", "def _test_cell_():\n", "\t#| echo: false\n", - "\ta = \"'a' is not polluting global scope\"\n", - "\treturn (a,\n", + "\tresult = \"complex parsing\"\n", + "\treturn (result,\n", "\t True) # %%testcell\n", "try:\n", "\t_ = _test_cell_()\n", @@ -469,7 +467,7 @@ { "data": { "text/plain": [ - "(\"'a' is not polluting global scope\", True)" + "('complex parsing', True)" ] }, "execution_count": null, @@ -480,559 +478,10 @@ "source": [ "%%testcell verbose\n", "#| echo: false\n", - "a = \"'a' is not polluting global scope\"\n", - "(a,\n", + "result = \"complex parsing\"\n", + "(result,\n", " True)\n", - "# this is a comment on last line" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Skip execution\n", - "It is possible to skip a cell execution using `skip` command. This is useful when you want to keep around the code but don't actually run it.\n", - "It's also possible to skip **all cells marked with `%%testcell`** using the following syntax: \n", - "`testcell.global_skip=True`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "%%testcell skip\n", - "raise ValueError('This should not be executed')\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "
\n", - " This cell has been skipped\n", - "
\n", - " " - ], - "text/plain": [ - "ℹ️ This cell has been skipped" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%testcell skip\n", - "#| echo: false\n", - "#| output: false\n", - "raise ValueError('This should not be executed')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ℹ️ This cell has been skipped\n" - ] - } - ], - "source": [ - "#| echo: false\n", - "print(testcell.skip_message_box.__repr__())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Run in isolation\n", - "\n", - "`%%testcelln` is a shortcut for `%%testcell noglobals` and executes the cell in complete isolation from the global scope. \n", - "This is very useful when you want to ensure that global variables or namespaces are not accessible within the cell." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "aaa = 'global variable'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "%%testcell\n", - "'aaa' in globals()\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "%%testcell \n", - "#| echo: false\n", - "'aaa' in globals()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "%%testcell noglobals\n", - "'aaa' in globals()\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "False" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%testcell noglobals\n", - "#| echo: false\n", - "'aaa' in globals()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "%%testcelln\n", - "'aaa' in globals()\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "False" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%testcelln\n", - "#| echo: false\n", - "'aaa' in globals()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "%%testcelln\n", - "globals().keys()\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "dict_keys(['__builtins__'])" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%testcelln\n", - "#| echo: false\n", - "globals().keys()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "With `%%testcelln` inside the cell, you'll be able to access only `__builtins__` (aka: standard python's functions). **It behaves like a notebook-in-notebook**." - ] - }, - { - "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": {}, - "source": [ - "**IMPORTANT**: this is *just wrapping your cell* and so it's still running on your main kernel. If you modify variables that have been created outside of this cell (aka: if you have side effects) this will not protect you." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'global variable'" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "aaa" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There is a dedicated syntax to enable passing variables to an isolated context and saving variables of objects back to the main context with the `(inputs)->(outputs)` syntax" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "%%testcelln (aaa)\n", - "# Input only \n", - "aaa # global variable\n", - "````" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'global variable'" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%testcelln (aaa)\n", - "#| echo: false\n", - "# Input only \n", - "aaa # global variable" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "%%testcelln (aaa)->(bbb,ccc) debug\n", - "# Input->Output and debug option\n", - "\n", - "print(aaa) # global variable\n", - "bbb=123\n", - "def ccc(): return 567\n", - "````" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/markdown": [ - "```python\n", - "\n", - "### BEGIN\n", - "def _test_cell_():\n", - "\tglobal bbb\n", - "\tglobal ccc\n", - "\t#| echo: false\n", - "\t# Input->Output and debug option\n", - "\t\n", - "\tprint(aaa) # global variable\n", - "\tbbb=123\n", - "\tdef ccc(): return 567\n", - "try:\n", - "\t_ = _test_cell_()\n", - "finally:\n", - "\tdel _test_cell_\n", - "if _ is not None: display(_)\n", - "### END\n", - "```" - ], - "text/plain": [ - "\n", - "### BEGIN\n", - "def _test_cell_():\n", - "\tglobal bbb\n", - "\tglobal ccc\n", - "\t#| echo: false\n", - "\t# Input->Output and debug option\n", - "\t\n", - "\tprint(aaa) # global variable\n", - "\tbbb=123\n", - "\tdef ccc(): return 567\n", - "try:\n", - "\t_ = _test_cell_()\n", - "finally:\n", - "\tdel _test_cell_\n", - "if _ is not None: display(_)\n", - "### END" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "global variable\n" - ] - }, - { - "data": { - "text/markdown": [ - "```python\n", - "\n", - "### GLOBALS UPDATE CODE:\n", - "global bbb; bbb=locals()[\"bbb\"]\n", - "global ccc; ccc=locals()[\"ccc\"]\n", - "###\n", - "```" - ], - "text/plain": [ - "\n", - "### GLOBALS UPDATE CODE:\n", - "global bbb; bbb=locals()[\"bbb\"]\n", - "global ccc; ccc=locals()[\"ccc\"]\n", - "###" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%testcelln (aaa)->(bbb,ccc) debug\n", - "#| echo: false\n", - "# Input->Output and debug option\n", - "\n", - "print(aaa) # global variable\n", - "bbb=123\n", - "def ccc(): return 567" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Variable bbb has been created in this context\n", - "assert bbb==123\n", - "assert ccc()==567\n", - "\n", - "# cleaup\n", - "del bbb,ccc" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```python\n", - "%%testcell \n", - "# WARNING: this will alter the state of global variable:\n", - "globals().update({'aaa' : 'modified global variable'});\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%testcell \n", - "#| echo: false\n", - "# WARNING: this will alter the state of global variable:\n", - "globals().update({'aaa' : 'modified global variable'});" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'modified global variable'" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "aaa" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "del aaa" + "# comment on last line" ] }, { @@ -1045,6 +494,7 @@ "+ DOCUMENTATION: [https://artste.github.io/testcell](https://artste.github.io/testcell)\n", "+ PYPI: [https://pypi.org/project/testcell](https://pypi.org/project/testcell)\n", "+ DETAILED DEMO: \n", + "+ USE CASE ZOO: \n", "+ LAUNCHING BLOG: [Introducing `%%testcell`](https://artste.github.io/blog/posts/introducing-testcell)\n", "+ COLAB DEMO: [testcell_demo.ipynb](https://colab.research.google.com/github/artste/testcell/blob/main/demo/testcell_demo.ipynb)\n", "+ KAGGLE SAMPLE NOTEBOOK: " From 87ffaecca1e72c2566ad865a9c74973216cbad4e Mon Sep 17 00:00:00 2001 From: Stefano Giomo Date: Thu, 6 Nov 2025 10:42:28 +0000 Subject: [PATCH 09/18] Results in demo, added testcell_zoo with lot of real use cases --- demo/testcell_demo.ipynb | 420 ++++++- demo/testcell_zoo.ipynb | 2491 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 2876 insertions(+), 35 deletions(-) create mode 100644 demo/testcell_zoo.ipynb diff --git a/demo/testcell_demo.ipynb b/demo/testcell_demo.ipynb index eb5a73d..8dc666b 100644 --- a/demo/testcell_demo.ipynb +++ b/demo/testcell_demo.ipynb @@ -20,7 +20,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "a9d36534", "metadata": {}, "outputs": [], @@ -30,7 +30,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "574fdb14", "metadata": {}, "outputs": [], @@ -50,7 +50,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "c4de6797", "metadata": {}, "outputs": [], @@ -65,10 +65,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "545b0118", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__builtin__', '__builtins__', '_ih', '_oh', '_dh', 'In', 'Out', 'get_ipython', 'exit', 'quit', 'open', '_', '__', '___', '__session__', '_i', '_ii', '_iii', '_i1', '_i2', 'testcell', '_i3', 'os', 'sample_variable', 'sample_function', '_i4'])" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "globals().keys()" ] @@ -85,10 +96,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "30027b0f", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "\"'a' is not polluting global scope\"" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "%%testcell\n", "a = \"'a' is not polluting global scope\"\n", @@ -97,7 +119,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "a135198e", "metadata": {}, "outputs": [], @@ -108,10 +130,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "3f369a4a", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "\"'a' is not polluting global scope\"" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "%%testcell\n", "a = \"'a' is not polluting global scope\"; a # it works as inline too even if there is a comment" @@ -119,7 +152,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "2ea4f41f", "metadata": {}, "outputs": [], @@ -138,10 +171,79 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "1e70eb60", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\t \n", + "\t SVG Logo\n", + "\t Designed for the SVG Logo Contest in 2006 by Harvey Rayner, and adopted by W3C in 2009. It is available under the Creative Commons license for those who have an SVG product or who are using SVG on their site.\n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t SVG Logo\n", + "\t 14-08-2009\n", + "\t \n", + "\t W3C\n", + "\t Harvey Rayner, designer\n", + "\t \n", + "\t See document description\n", + "\t \n", + "\t image/svg+xml\n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t \n", + "\t " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "%%testcell\n", "from IPython.display import SVG,HTML\n", @@ -225,10 +327,53 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "9e576724", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/markdown": [ + "```python\n", + "### BEGIN\n", + "def _test_cell_():\n", + "\tb = 123\n", + "\treturn b # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "_ # This will be added to global scope\n", + "### END\n", + "```" + ], + "text/plain": [ + "### BEGIN\n", + "def _test_cell_():\n", + "\tb = 123\n", + "\treturn b # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "_ # This will be added to global scope\n", + "### END" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "123" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "%%testcell verbose\n", "b = 123\n", @@ -247,10 +392,43 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "3065d361", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/markdown": [ + "```python\n", + "### BEGIN\n", + "def _test_cell_():\n", + "\tb = 123\n", + "\treturn print('You should not see this message on the output') # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "if _ is not None: display(_)\n", + "### END\n", + "```" + ], + "text/plain": [ + "### BEGIN\n", + "def _test_cell_():\n", + "\tb = 123\n", + "\treturn print('You should not see this message on the output') # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "if _ is not None: display(_)\n", + "### END" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "%%testcell dryrun\n", "b = 123\n", @@ -270,10 +448,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "19ab37cc", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "123" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "%%testcell noreturn\n", "b = 123\n", @@ -293,10 +481,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "b0a2abfd", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dict_keys(['__builtins__'])\n" + ] + } + ], "source": [ "%%testcelln\n", "assert list(globals().keys()) == ['__builtins__']\n", @@ -316,10 +512,27 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "id": "c8fe2586-4dec-48f3-a486-5e7b6d46d10b", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " This cell has been skipped\n", + "
\n", + " " + ], + "text/plain": [ + "ℹ️ This cell has been skipped" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "%%testcell skip\n", "raise ValueError('This should not be executed')" @@ -337,10 +550,37 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "cedecb62-35d2-4cdd-ac28-2473e0093d8e", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " testcell\n", + "
\n", + " " + ], + "text/plain": [ + "🟡 testcell" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "'This is a banner for regular %%testcell cell'" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "%%testcell banner\n", "'This is a banner for regular %%testcell cell'" @@ -348,10 +588,36 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "c9584772-e407-415e-a6bc-24ee44c66455", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " testcell noglobals\n", + "
\n", + " " + ], + "text/plain": [ + "🟢 testcell noglobals" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "'This is a banner for %%testcell noglobals cell'" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "%%testcelln banner\n", "'This is a banner for %%testcell noglobals cell'" @@ -369,10 +635,74 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "id": "24c7c686-7a9a-458c-b486-ba353c3c4b52", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/markdown": [ + "```python\n", + "### BEGIN\n", + "def _test_cell_():\n", + "\tglobal new_func\n", + "\tdef new_func(): return sample_variable\n", + "\t\n", + "\treturn new_func() # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "if _ is not None: display(_)\n", + "### END\n", + "```" + ], + "text/plain": [ + "### BEGIN\n", + "def _test_cell_():\n", + "\tglobal new_func\n", + "\tdef new_func(): return sample_variable\n", + "\t\n", + "\treturn new_func() # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "if _ is not None: display(_)\n", + "### END" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "'this is a sample variable'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/markdown": [ + "```python\n", + "### GLOBALS UPDATE CODE:\n", + "global new_func; new_func=locals()[\"new_func\"]\n", + "###\n", + "```" + ], + "text/plain": [ + "### GLOBALS UPDATE CODE:\n", + "global new_func; new_func=locals()[\"new_func\"]\n", + "###" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "%%testcelln (sample_variable)->(new_func) debug\n", "def new_func(): return sample_variable\n", @@ -382,7 +712,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "id": "12387ed0-89c9-45dd-9d2f-ab7c0673ceef", "metadata": {}, "outputs": [], @@ -401,10 +731,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "id": "634d2155-545b-4aae-957e-11c3491d5e6d", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "'this is a sample variable'" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "%%testcelln (sample_variable)\n", "def new_func(): return sample_variable\n", @@ -421,10 +761,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "id": "fb8ba9cb-6082-4071-89e5-9d8dbdf118fb", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "123" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "%%testcelln ->(new_func)\n", "def new_func(): return 123\n", @@ -433,7 +783,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "id": "ddc07336-e208-440f-969d-b88468a3da48", "metadata": {}, "outputs": [], diff --git a/demo/testcell_zoo.ipynb b/demo/testcell_zoo.ipynb new file mode 100644 index 0000000..0795cc3 --- /dev/null +++ b/demo/testcell_zoo.ipynb @@ -0,0 +1,2491 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f5b071d3-d836-4128-9596-ce3f2f4d21c9", + "metadata": {}, + "source": [ + "# Testcell Zoo" + ] + }, + { + "cell_type": "markdown", + "id": "40eb3af8-1dec-428d-8e22-40374f63245a", + "metadata": {}, + "source": [ + "## Introduction\n", + "\n", + "This notebook demonstrates practical use cases for `%%testcell`. Each example is self-contained and can be run independently after loading the fixtures." + ] + }, + { + "cell_type": "markdown", + "id": "d75a2d0b", + "metadata": {}, + "source": [ + "### install\n", + "\n", + "First of all, install and import `testcell`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "92505d86-5f42-4f5f-8d3c-6008b6b113be", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install testcell" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "db3d55aa-237c-4033-8ace-621202fcfa4d", + "metadata": {}, + "outputs": [], + "source": [ + "# Install dependecies used in this notebook\n", + "!pip install numpy pandas matplotlib" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "574fdb14", + "metadata": {}, + "outputs": [], + "source": [ + "import testcell" + ] + }, + { + "cell_type": "markdown", + "id": "a5cbdc78-47e9-470c-9187-f00c9c229527", + "metadata": {}, + "source": [ + "### Fixtures\n", + "\n", + "Common objects used throughout the examples:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9818ce17-f533-4fa3-a442-68fcf4908b2c", + "metadata": {}, + "outputs": [], + "source": [ + "# Simple variable\n", + "global_variable = \"I'm a global variable\"\n", + "\n", + "# Simple list\n", + "global_list = [1, 2, 3, 5, 7, 11, 13]\n", + "\n", + "# Simple function\n", + "def global_function(n):\n", + " return n*2\n", + "\n", + "# Simple class\n", + "class GlobalClass:\n", + " def one(self):\n", + " return 37\n", + " \n", + " def two(self):\n", + " return \"method of global class\"" + ] + }, + { + "cell_type": "markdown", + "id": "574ff0f3-d605-4c0d-8ed4-463e0adfc3d3", + "metadata": {}, + "source": [ + "## Documentation\n", + "\n", + "Individual options and their behavior:" + ] + }, + { + "cell_type": "markdown", + "id": "f9579085-fa36-4f1d-a4b8-91aa4c50a209", + "metadata": {}, + "source": [ + "### option: `verbose`\n", + "Shows the generated wrapper function before execution." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "1849ad22-85f6-4f7d-9ab2-25c847bfcf1f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```python\n", + "### BEGIN\n", + "def _test_cell_():\n", + "\t\n", + "\treturn global_function(3) * 2 # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "_ # This will be added to global scope\n", + "### END\n", + "```" + ], + "text/plain": [ + "### BEGIN\n", + "def _test_cell_():\n", + "\t\n", + "\treturn global_function(3) * 2 # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "_ # This will be added to global scope\n", + "### END" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "12" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%testcell verbose\n", + "global_function(3) * 2" + ] + }, + { + "cell_type": "markdown", + "id": "49c32487-bbf7-4d6a-bc57-50597809b3fe", + "metadata": {}, + "source": [ + "### option `dryrun`\n", + "Shows the wrapper code without executing it." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "e96b57e1-31f9-4af6-b673-fc8387e32007", + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```python\n", + "### BEGIN\n", + "def _test_cell_():\n", + "\t\n", + "\treturn global_function(3) * 2 # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "if _ is not None: display(_)\n", + "### END\n", + "```" + ], + "text/plain": [ + "### BEGIN\n", + "def _test_cell_():\n", + "\t\n", + "\treturn global_function(3) * 2 # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "if _ is not None: display(_)\n", + "### END" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcell dryrun\n", + "global_function(3) * 2" + ] + }, + { + "cell_type": "markdown", + "id": "bad62496-6d10-4627-86af-2abc3ba9a752", + "metadata": {}, + "source": [ + "### option `skip`\n", + "Skips cell execution entirely." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "ff0bb19d-d263-46da-9176-d2abee049f8f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " This cell has been skipped\n", + "
\n", + " " + ], + "text/plain": [ + "ℹ️ This cell has been skipped" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcell skip\n", + "raise ValueError(\"This won't execute\")" + ] + }, + { + "cell_type": "markdown", + "id": "762017da-2a77-458c-bb94-843239baafaf", + "metadata": {}, + "source": [ + "### option `banner`\n", + "Displays a visual marker at the top of output." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "9357a4b7-d2fa-455e-b0dd-61efc76c5b4f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " testcell\n", + "
\n", + " " + ], + "text/plain": [ + "🟡 testcell" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "6" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%testcell banner\n", + "global_function(3)" + ] + }, + { + "cell_type": "markdown", + "id": "96abea99-be60-4244-a00c-2fccd28cbd65", + "metadata": {}, + "source": [ + "### option `noglobals` (or `%%testcelln`)\n", + "Executes with zero access to notebook globals - only `__builtins__` available." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "c8f4ee3c-750c-4e84-a321-2a5be1d976a1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "global_function not available here\n" + ] + } + ], + "source": [ + "%%testcelln\n", + "try:\n", + " global_function(3) # This will fail\n", + "except NameError:\n", + " print('global_function not available here')" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "fd7f6e61-b159-4d81-9b89-910fbae42c34", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "global_function not available here\n" + ] + } + ], + "source": [ + "%%testcell noglobals\n", + "try:\n", + " global_function(3) # This will fail\n", + "except NameError:\n", + " print('global_function not available here')" + ] + }, + { + "cell_type": "markdown", + "id": "1963ed9c-026d-437e-9ca6-763a2c637e9a", + "metadata": {}, + "source": [ + "### option `(inputs)` syntax\n", + "Explicitly control what enters in the isolated context." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "48412b54-b2ce-4cdb-8a2e-7dac4a3a2d98", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[2, 4, 6, 10, 14, 22, 26]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln (global_list)\n", + "[x * 2 for x in global_list]" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "bffb1ef7-4c40-4d82-8b37-e5d72e93079e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "37" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln (GlobalClass)\n", + "GlobalClass().one()" + ] + }, + { + "cell_type": "markdown", + "id": "77e834b2-6c40-4b61-8a43-3734656e861f", + "metadata": {}, + "source": [ + "### option `(inputs)->(outputs)` syntax\n", + "Explicitly control what enters and exits the isolated context." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "008e7a4c-86c9-46da-8ddc-cfef3e482fc2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[2, 4, 6, 10, 14, 22, 26]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln (global_list)->(double_list)\n", + "double_list = [x * 2 for x in global_list]\n", + "double_list" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "e0426272-2f12-4658-ad08-eba79b63cc43", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2, 4, 6, 10, 14, 22, 26]\n" + ] + } + ], + "source": [ + "print(double_list) # double_list now exists in the global namespace\n", + "del double_list # cleanin it up" + ] + }, + { + "cell_type": "markdown", + "id": "e3b33f77-cdb7-4ccb-9896-edb5070726ea", + "metadata": {}, + "source": [ + "### option `debug`\n", + "Shows wrapper code AND globals update operations." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "4773e185-9605-4567-bab4-1abc910f353d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```python\n", + "### BEGIN\n", + "def _test_cell_():\n", + "\tglobal new_var\n", + "\tnew_var = global_variable.upper()\n", + "\treturn new_var # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "if _ is not None: display(_)\n", + "### END\n", + "```" + ], + "text/plain": [ + "### BEGIN\n", + "def _test_cell_():\n", + "\tglobal new_var\n", + "\tnew_var = global_variable.upper()\n", + "\treturn new_var # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "if _ is not None: display(_)\n", + "### END" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "\"I'M A GLOBAL VARIABLE\"" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/markdown": [ + "```python\n", + "### GLOBALS UPDATE CODE:\n", + "global new_var; new_var=locals()[\"new_var\"]\n", + "###\n", + "```" + ], + "text/plain": [ + "### GLOBALS UPDATE CODE:\n", + "global new_var; new_var=locals()[\"new_var\"]\n", + "###" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln (global_variable)->(new_var) debug\n", + "new_var = global_variable.upper()\n", + "new_var" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "94a81e1e-9bc7-46a9-b5c9-3c67327daced", + "metadata": {}, + "outputs": [], + "source": [ + "del new_var" + ] + }, + { + "cell_type": "markdown", + "id": "dc485ee8-b811-497e-9baf-7627d4a1f3b1", + "metadata": {}, + "source": [ + "## Use Case Scenarios" + ] + }, + { + "cell_type": "markdown", + "id": "23d09c78-8ff9-449d-b846-2128fc5b526c", + "metadata": {}, + "source": [ + "### Basic Isolation" + ] + }, + { + "cell_type": "markdown", + "id": "4e1bfff7-a9d6-4b12-b6c0-7ad0adf1a502", + "metadata": {}, + "source": [ + "#### Testing without pollution\n", + "\n", + "Test a function with various inputs without cluttering globals with test values." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "1c6d0120-9c43-427b-b26d-f21f74138fb5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Results: [2, 10, 20, 200]\n" + ] + }, + { + "data": { + "text/plain": [ + "np.float64(58.0)" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%testcell\n", + "import numpy as np\n", + "\n", + "# Test global_function with different inputs\n", + "test_cases = [1, 5, 10, 100]\n", + "results = [global_function(x) for x in test_cases]\n", + "print(f\"Results: {results}\")\n", + "np.mean(results) # test_cases and results don't leak to globals" + ] + }, + { + "cell_type": "markdown", + "id": "06669ac7-51fa-4187-b607-b23436058a87", + "metadata": {}, + "source": [ + "#### Quick code prototyping\n", + "\n", + "Experiment with different approaches without commitment." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "a465106a-18f1-49b2-a012-a20f13504437", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Approach 1: [2.0, 3.3333333333333335, 5.0, 7.666666666666667, 10.333333333333334]\n", + "Approach 2: [2.0, 3.3333333333333335, 5.0, 7.666666666666667, 10.333333333333334]\n" + ] + } + ], + "source": [ + "%%testcell\n", + "import pandas as pd\n", + "\n", + "# Trying different ways to process global_list\n", + "approach_1 = pd.Series(global_list).rolling(3).mean().dropna()\n", + "approach_2 = [sum(global_list[i:i+3])/3 for i in range(len(global_list)-2)]\n", + "print(f\"Approach 1: {approach_1.tolist()}\")\n", + "print(f\"Approach 2: {approach_2}\")\n", + "# Nothing saved - decide later which approach to keep" + ] + }, + { + "cell_type": "markdown", + "id": "4227e7c2-8abb-43fd-8a91-724267bd18b7", + "metadata": {}, + "source": [ + "#### Stack Overflow snippet testing\n", + "\n", + "Test SO answers in isolation to catch hidden dependencies." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "e3887d3e-b47d-4abb-9a06-86a7b399f8cd", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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vra+vh6jRo3CyJEjMWnSJISGhqKqqgrr1q3Dzp07MW3aNAwdOhQAoFAo8N1332HChAlISEhoVcl47dq1OHDgAJ544glMnz69w/d88cUXsWPHDkyZMgVhYWEoKSnB+++/j5CQECQkJFzXZ3GpqKgoJCQkYN68edBqtXj77bfh4+ODp556qtV+KpUKycnJuP/++xEXF4fffvsNv/zyC/71r3+Zu546+3sAALGxsfjggw/w8ssvIyoqCv7+/rjpppu67LyIrIK4k7iI6Gr9cZq4yUcffSTExsYKzs7Ogru7uzBw4EDhqaeeEgoKCgRBEIQDBw4I9913n9CzZ09BqVQK/v7+wq233irs37+/1XH27NkjxMbGCgqFotWU8famic+fP/+yWMLCwoT777+/1baUlBRh6NChgkKhECIjI4WPP/5YeOKJJwSVStXh+TY3NwsrV64Upk2bJoSFhQlKpVJwcXERhg4dKrz55putpmiblJSUCAsXLhSioqIEpVIpeHp6ComJiR1ODf9jrFOnThWCg4MFhUIhBAcHC/fdd59w6tQp8z6maeLffvttq9eapoB/9tln5m3tTRN/8803hf/85z9CaGiooFQqhTFjxgiHDh1qdbz7779fcHV1FU6fPi1MnDhRcHFxEQICAoQlS5YIer3+stiv9HsgCIJQVFQkTJkyRXB3dxcAcMo42SWuRUVEopk2bRqOHTvW5lgUe5abm4tevXrhzTffxD//+c8O9509eza+++471NbWdlN0RPaBY3CIqFs0NDS0+nd2djZ+/fVXLhNARBbBMThE1C0iIiIwe/ZsRERE4Ny5c/jggw+gUCguG29CRNQVmOAQUbeYNGkSvvzySxQVFUGpVCI+Ph6vvvpqm8XuiIiuF8fgEBERkd3hGBwiIiKyO0xwiIiIyO445Bgcg8GAgoICuLu7s2Q5ERGRjRAEATU1NQgODoZU2nEbjUMmOAUFBQgNDRU7DCIiIroG+fn5CAkJ6XAfh0xw3N3dARg/IA8PD5GjISIios7QaDQIDQ01f493xCETHFO3lIeHBxMcIiIiG9OZ4SUcZExERER2hwkOERER2R0mOERERGR3mOAQERGR3WGCQ0RERHaHCQ4RERHZHSY4REREZHeY4BAREZHdYYJDREREdocJDhEREdkdiyY4O3bswG233Ybg4GBIJBKsW7fuiq/Ztm0b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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "# SO snippet for quick visualization\n", + "x = np.linspace(0, 10, 100)\n", + "y = np.sin(x)\n", + "plt.plot(x, y)\n", + "plt.title(\"Testing SO snippet\")\n", + "plt.show() # Completely isolated - no globals needed or leaked" + ] + }, + { + "cell_type": "markdown", + "id": "dc06aadf-a3e3-4036-8fbd-8b02ab7e92c2", + "metadata": {}, + "source": [ + "#### Safe LLM suggested code experimentation\n", + "\n", + "Try code from ChatGPT/Claude in complete isolation - verify it works standalone." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "cd8d15ba-ee02-43a8-90ca-7bd1a28c2ca2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ABratio
count7.0000007.0000007.000000
mean6.00000054.0000006.000000
std4.58257665.7190994.582576
min1.0000001.0000001.000000
25%2.5000006.5000002.500000
50%5.00000025.0000005.000000
75%9.00000085.0000009.000000
max13.000000169.00000013.000000
\n", + "
" + ], + "text/plain": [ + " A B ratio\n", + "count 7.000000 7.000000 7.000000\n", + "mean 6.000000 54.000000 6.000000\n", + "std 4.582576 65.719099 4.582576\n", + "min 1.000000 1.000000 1.000000\n", + "25% 2.500000 6.500000 2.500000\n", + "50% 5.000000 25.000000 5.000000\n", + "75% 9.000000 85.000000 9.000000\n", + "max 13.000000 169.000000 13.000000" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln (global_list)\n", + "import pandas as pd\n", + "\n", + "# Suppose an LLM suggested this snippet\n", + "data = {'A': global_list, 'B': [x**2 for x in global_list]}\n", + "df = pd.DataFrame(data)\n", + "df['ratio'] = df['B'] / df['A']\n", + "df.describe() # Runs in isolation, only global_list was passed in" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "5f8576c0-9a7a-40bb-8b4a-647ff205aa1e", + "metadata": {}, + "outputs": [], + "source": [ + "assert 'df' not in locals() # Ensure df is not in the local scope\n", + "assert 'df' not in globals() # Ensure df is not in the global scope" + ] + }, + { + "cell_type": "markdown", + "id": "84c2b7eb-919d-4a92-a567-71243841a53e", + "metadata": {}, + "source": [ + "### Visualization & Analysis" + ] + }, + { + "cell_type": "markdown", + "id": "dc9b82fc-c4e2-432d-a59e-33fc5794a99b", + "metadata": {}, + "source": [ + "#### Matplotlib without clutter\n", + "\n", + "Create plots without importing `plt`, `fig`, or `ax` into globals. For example, this could be a code snippet from the documentation that you want to understand and experiment with." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "f3e757d9-b253-43d9-bf67-85e051b5faba", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAwoAAAF2CAYAAADOR2+yAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsnXe8HUX1wL8zW257/SXvvYRUCCSETgyQkIROpCnSpEiJKAoBaYqChS7lh9KMgEpRiiCIBZAugnQMIB0E0kN68votuzu/P/a2vXdf8nLz0ufL55J3z87szu6998ycmXPOCKWUQqPRaDQajUaj0WiKkOu7ARqNRqPRaDQajWbDQxsKGo1Go9FoNBqNpgxtKGg0Go1Go9FoNJoytKGg0Wg0Go1Go9FoytCGgkaj0Wg0Go1GoylDGwoajUaj0Wg0Go2mDG0oaDQajUaj0Wg0mjK0oaDRaDQajUaj0WjK0IaCRqPRaDQajUajKUMbCppNnlNOOYWqqqpelRVCcMkll6zV9tx1110IIZg5c+ZavY5Go9m4+Ne//oUQgn/961+9LvvQQw+t/YaFsC505dpmdZ73xobuZzR9hTYUNhJyP/r//Oc/ocf33ntvtt9++3XcKo1Go9EA/OlPf0IIwV/+8peyYzvttBNCCJ577rmyY0OGDGH8+PE9nve+++7jhhtu6MumajQaTa/RhoJGU0R3dzc/+clP1uo1TjzxRLq7uxk6dOhavY5Go1l3TJgwAYAXX3wxIG9ra+O9997DNE1eeumlwLE5c+YwZ86cfN1JkybR3d3NpEmT8mW0oaDRaNYn5vpugEazIRGNRtf6NQzDwDCMtX4djUaz7hg4cCDDhw8vMxReeeUVlFIcffTRZcdy73OGgpRyneggzdqhs7OTRCKxvpuh0fQpekVhE+eee+5hzJgxxGIxGhoaOPbYY5kzZ07++J133okQgjvuuCNQ7+c//zlCCP7xj3/kZddddx3jx4+nsbGRWCzGmDFjQv1ju7u7+d73vke/fv2orq7mK1/5CvPmzQv1aX3rrbc46KCDqKmpoaqqiv32249XX301UCbndvXSSy9x3nnn0b9/fxKJBF/72tdYvHhxr5/F559/zuTJk0kkEgwcOJDLLrsMpVSgTGkbL7nkEoQQfPrpp5xyyinU1dVRW1vLlClT6OrqKqt75pln8te//pXtt9+eSCTCdtttxxNPPBF6P8W+o8OGDePQQw/lxRdfZLfddiMajbLlllvyhz/8oew+3nnnHfbaay9isRiDBg3iiiuuyH+O2h9Vo1l/TJgwgbfeeovu7u687KWXXmK77bbjoIMO4tVXX8XzvMAxIQR77rknUO4zv/fee/PYY48xa9YshBAIIRg2bFjgmp7nceWVVzJo0CCi0Sj77bcfn3766SrbOmvWLM444wxGjhxJLBajsbGRo48+eo10SG/0Oay5Dvvoo4845phj6N+/P7FYjJEjR/LjH/+4oraE8eCDD+b7zX79+vGNb3yDefPmBcrkYt8+++wzDj74YKqrqznhhBN6PGd7ezvnnHMOw4YNIxKJ0NTUxAEHHMCbb74ZKPfaa69x8MEHU19fTyKRYMcdd+TGG2/MH3/nnXc45ZRT2HLLLYlGo7S0tPDNb36TpUuX9ureHn/8cSZOnEgikaC6uppDDjmE999/v1d1NZsnekVhI6O1tZUlS5aUyTOZTJnsyiuv5Kc//SnHHHMM3/rWt1i8eDE333wzkyZN4q233qKuro4pU6bw8MMPc95553HAAQcwePBg3n33XS699FJOPfVUDj744Pz5brzxRr7yla9wwgknkE6nuf/++zn66KN59NFHOeSQQ/LlTjnlFP70pz9x4oknsscee/D8888Hjud4//33mThxIjU1NVxwwQVYlsVtt93G3nvvzfPPP8/uu+8eKH/WWWdRX1/PxRdfzMyZM7nhhhs488wzeeCBB1b53FzX5ctf/jJ77LEH1157LU888QQXX3wxjuNw2WWXrbL+Mcccw/Dhw7nqqqt48803+d3vfkdTUxPXXHNNoNyLL77Iww8/zBlnnEF1dTU33XQTRx55JLNnz6axsXGl1/j000856qijOPXUUzn55JO54447OOWUUxgzZgzbbbcdAPPmzWOfffZBCMGFF15IIpHgd7/7HZFIZJX3oNFo1i4TJkzg7rvv5rXXXmPvvfcGfGNg/PjxjB8/ntbWVt577z123HHH/LFRo0b1qBt+/OMf09rayty5c7n++usByhIzXH311Ugp+f73v09rayvXXnstJ5xwAq+99tpK2/rGG2/w8ssvc+yxxzJo0CBmzpzJLbfcwt57780HH3xAPB5frXvvrT5fUx32zjvvMHHiRCzL4rTTTmPYsGF89tlnPPLII1x55ZWr1ZYw7rrrLqZMmcLYsWO56qqrWLhwITfeeCMvvfRSvt/M4TgOkydPZsKECVx33XUrfWbf/e53eeihhzjzzDMZPXo0S5cu5cUXX+TDDz9k1113BeDpp5/m0EMPZcCAAZx99tm0tLTw4Ycf8uijj3L22Wfny3z++edMmTKFlpYW3n//fX7zm9/w/vvv8+qrryKE6LENd999NyeffDKTJ0/mmmuuoauri1tuuSVv4JYaoRoNAEqzUXDnnXcqYKWv7bbbLl9+5syZyjAMdeWVVwbO8+677yrTNAPyL774QjU0NKgDDjhApVIptcsuu6ghQ4ao1tbWQN2urq7A+3Q6rbbffnu177775mXTp09XgDrnnHMCZU855RQFqIsvvjgvO/zww5Vt2+qzzz7Ly+bPn6+qq6vVpEmTyu59//33V57n5eXnnnuuMgxDrVixYqXP7uSTT1aAOuuss/Iyz/PUIYccomzbVosXL87LS9t48cUXK0B985vfDJzza1/7mmpsbAzIAGXbtvr000/zsv/+978KUDfffHPZ/cyYMSMvGzp0qALUCy+8kJctWrRIRSIRdf755+dlZ511lhJCqLfeeisvW7p0qWpoaCg7p0ajWbe8//77ClCXX365UkqpTCajEomE+v3vf6+UUqq5uVlNmzZNKaVUW1ubMgxDffvb387Xf+655xSgnnvuubzskEMOUUOHDi27Vq7stttuq1KpVF5+4403KkC9++67K21rqT5XSqlXXnlFAeoPf/jDKu+1Un2+pjps0qRJqrq6Ws2aNSsgL+4betuW0uedTqdVU1OT2n777VV3d3e+3KOPPqoA9bOf/Swvy/UrP/rRj1ba3hy1tbVq6tSpPR53HEcNHz5cDR06VC1fvrzHewv73P74xz+W9R+l/Ux7e7uqq6sLfN+UUmrBggWqtra2TK7R5NCuRxsZ06ZN4+mnny575Waocjz88MN4nscxxxzDkiVL8q+Wlha23nrrQPaNlpaW/HknTpzI22+/zR133EFNTU3gnLFYLP/38uXLaW1tZeLEiYGl05ybzRlnnBGoe9ZZZwXeu67LU089xeGHH86WW26Zlw8YMIDjjz+eF198kba2tkCd0047LTBbMnHiRFzXZdasWb16dmeeeWb+75ybUDqd5plnnlll3e9+97uB9xMnTmTp0qVlbdx///3Zaqut8u933HFHampq+Pzzz1d5jdGjRzNx4sT8+/79+zNy5MhA3SeeeIJx48ax884752UNDQ0rXfLWaDTrhm233ZbGxsZ87MF///tfOjs781mNxo8fnw9ofuWVV3BdNx+fUClTpkzBtu38+5wOWZXOKdbnmUyGpUuXMmLECOrq6srcYVbF6ujzNdFhixcv5oUXXuCb3/wmQ4YMCRzL9Q2V9C05/vOf/7Bo0SLOOOOMQKzIIYccwqhRo3jsscfK6px++umrbDdAXV0dr732GvPnzw89/tZbbzFjxgzOOeecwKpF8b1B8HNLJpMsWbKEPfbYA2Cln9vTTz/NihUrOO644wJjAsMw2H333UMzcmk0oF2PNjp22203vvSlL5XJ6+vrAy5J//vf/1BKsfXWW4eex7KswPtjjz2We+65h8cee4zTTjuN/fbbr6zOo48+yhVXXMHbb79NKpXKy4uV2KxZs5BSMnz48EDdESNGBN4vXryYrq4uRo4cWXadbbfdFs/zmDNnTt7lBijrGOrr6wHfaFkVUspApwGwzTbbAPTKJ3Zl1y42qErL5cr2po29qTtr1izGjRtXVq70+Wo0mnWPEILx48fzwgsv4HkeL730Ek1NTfnf5/jx4/nVr34FkDcY1tRQqFQvdnd3c9VVV3HnnXcyb968QLxWa2vrarVhdfT5muiwnPGzslTglfQtOXKTTmF1R40aVRaMbpomgwYNWmW7Aa699lpOPvlkBg8ezJgxYzj44IM56aST8v3SZ599tsp7A1i2bBmXXnop999/P4sWLQocW9nn9r///Q+AfffdN/R46cSgRpNDGwqbKJ7nIYTg8ccfD82wU+rnunTp0vweDR988AGe5yFlYcHp3//+N1/5yleYNGkSv/71rxkwYACWZXHnnXdy3333rd2bydJTpiBVEpC8Pq+9Jm1cn/en0Wj6hgkTJvDII4/w7rvv5uMTcowfP54f/OAHzJs3jxdffJGBAweWTWCsLpXqjbPOOos777yTc845h3HjxlFbW4sQgmOPPTYQcK3pmUgkEugnV8YxxxzDxIkT+ctf/sJTTz3F//3f/3HNNdfw8MMPc9BBB/X6mscccwwvv/wyP/jBD9h5552pqqrC8zy+/OUvr/Rzyx27++67aWlpKTtumno4qAlHfzM2UbbaaiuUUgwfPjw/c74ypk6dSnt7O1dddRUXXnghN9xwA+edd17++J///Gei0ShPPvlkIOjszjvvDJxn6NCheJ7HjBkzAqsZpVk4+vfvTzwe5+OPPy5ry0cffYSUksGDB/f6fleF53l8/vnngWfxySefAGxUAVxDhw4NzWjSmywnGo1m7VO8n8JLL73EOeeckz82ZswYIpEI//rXv/LZbVbFyoJT14SHHnqIk08+mV/84hd5WTKZZMWKFat9rtXR52uiw3JG1XvvvdcnbSklt7fNxx9/XDbz/vHHH6/x3jcDBgzgjDPO4IwzzmDRokXsuuuuXHnllRx00EF5l9X33nuP/fffP7T+8uXLefbZZ7n00kv52c9+lpfnVgtWRu78TU1NPZ5fowlDxyhsohxxxBEYhsGll15aNrOklAqkUnvooYd44IEHuPrqq/nRj37Esccey09+8pP8QBr8WSshBK7r5mUzZ87kr3/9a+DckydPBuDXv/51QH7zzTcH3huGwYEHHsjf/va3gOvPwoULue+++5gwYUKfL4XmlvzBfwa/+tWvsCwr1M1qQ2Xy5Mm88sorvP3223nZsmXLuPfee9dfozQaTZ4vfelLRKNR7r33XubNmxdYUYhEIuy6665MmzaNzs7OXrkdJRKJ1XYF6g2GYZT1DTfffHNAx6/OuXqrz9dEh/Xv359JkyZxxx13MHv27MCx3L2sSd/ypS99iaamJm699daAe+3jjz/Ohx9+GJq9rze4rlv2GTY1NTFw4MD8dXbddVeGDx/ODTfcUGasFd9b8fscvdmQb/LkydTU1PDzn/88NEvi6qQa12xe6BWFTZStttqKK664ggsvvJCZM2dy+OGHU11dzYwZM/jLX/7Caaedxve//30WLVrE6aefzj777JMP9v3Vr37Fc889xymnnMKLL76IlJJDDjmEX/7yl3z5y1/m+OOPZ9GiRUybNo0RI0bwzjvv5K87ZswYjjzySG644QaWLl2aT4+aMzqKZ8euuOIKnn76aSZMmMAZZ5yBaZrcdtttpFIprr322j59HtFolCeeeIKTTz6Z3Xffnccff5zHHnuMiy66iP79+/fptdYmF1xwAffccw8HHHAAZ511Vj614JAhQ1i2bNlam33UaDS9w7Ztxo4dy7///W8ikQhjxowJHB8/fnx+Fr83hsKYMWN44IEHOO+88xg7dixVVVUcdthha9zOQw89lLvvvpva2lpGjx7NK6+8wjPPPLPKNM490Vt9vqY67KabbmLChAnsuuuunHbaaQwfPpyZM2fy2GOP5Y2PSvsWy7K45pprmDJlCnvttRfHHXdcPj3qsGHDOPfccyt6Nu3t7QwaNIijjjqKnXbaiaqqKp555hneeOON/HdBSsktt9zCYYcdxs4778yUKVMYMGAAH330Ee+//z5PPvkkNTU1TJo0iWuvvZZMJsMWW2zBU089xYwZM1bZhpqaGm655RZOPPFEdt11V4499lj69+/P7Nmzeeyxx9hzzz0Dk2kaTZ71kGlJUwG5VGdvvPFG6PG99torkB41x5///Gc1YcIElUgkVCKRUKNGjVJTp05VH3/8sVJKqSOOOEJVV1ermTNnBur97W9/U4C65ppr8rLbb79dbb311ioSiahRo0apO++8M58+tJjOzk41depU1dDQoKqqqtThhx+uPv74YwWoq6++OlD2zTffVJMnT1ZVVVUqHo+rffbZR7388su9uvewVIJhnHzyySqRSKjPPvtMHXjggSoej6vm5mZ18cUXK9d1A2XpIT1qcQrV4jYVp/IDQtPfDR06VJ188skrrTt06FB1yCGHlNXda6+91F577RWQvfXWW2rixIkqEomoQYMGqauuukrddNNNClALFixY6bPQaDRrnwsvvFABavz48WXHHn74YQWo6upq5ThO4FiYTuvo6FDHH3+8qqurU0A+VWqu7IMPPhg4x4wZMxSg7rzzzpW2cfny5WrKlCmqX79+qqqqSk2ePFl99NFHZfqqJ0p1pVK90+dKrbkOe++999TXvvY1VVdXp6LRqBo5cqT66U9/utpt6akPeeCBB9Quu+yiIpGIamhoUCeccIKaO3duoEyuX+kNqVRK/eAHP1A77bSTqq6uVolEQu20007q17/+dVnZF198UR1wwAH5cjvuuGMgvfbcuXPz915bW6uOPvpoNX/+/LLPI6yfyd3z5MmTVW1trYpGo2qrrbZSp5xyivrPf/7Tq3vRbH4IpXSkpGbt8/bbb7PLLrtwzz336FSea4FzzjmH2267jY6Ojh6DGzUajWZDReswjWbDRMcoaPqc7u7uMtkNN9yAlJJJkyathxZtWpQ+36VLl3L33XczYcIE3cFqNJoNHq3DNJqNBx2joOlzrr32WqZPn84+++yDaZo8/vjjPP7445x22ml9msloc2XcuHHsvffebLvttixcuJDbb7+dtrY2fvrT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12hrawegra2dww8/kn33PYAlS5YAvl741inncth+p7Ngvp+CNZN2uOi8G/jB967r69vskRNP/AZPPvkP6uvrATBNk1/84lpeeOE5WlpaAN9I+NnPfsJf/vLQOmtXRegVhYpYqzEKTzwR/PHcddddNDU1MX36dCZNmkRrayu333479913H/vuuy8Ad955J9tuuy2vvvoqe+yxx9psnkazXtArCJsf2223Hc8880z+fS6oEPzJkscee4wHH3yQ2tpazjzzTI444oh8qkHXdTnkkENoaWnh5Zdf5osvvuCkk07Csix+/vOfr/N70Wg2JzzP45//fK5MLoTg29/+Ftdee/VK6//7sbcByNkYnqdY4c7Nn9s/VjBAimURoy6bXSmYovOpf7wEN/+govuphAMPPIAPPniHa6+9juOO+zpjxowB4L333ubqq6/l0EMPZsKECeusPZp1yzoNZm5tbQWgoaEBgOnTp5PJZNh///3zZUaNGsWQIUN45ZVXtKGg0WjWGsJTFeXIFt7qrwCZppmffSumN5MlTz31FB988AHPPPMMzc3N7Lzzzlx++eX88Ic/5JJLLtlodkXVaDYlpJRss83WFdbe+CaLmpqauO66awOyuro6rr5645msqETnV6LvNzXWWTCz53mcc8457Lnnnmy//fYALFiwANu2y6L+m5ubWbBgQeh5UqkUbW1tgZdGo9GsNmvoelSqh1KpVI+X+t///sfAgQPZcsstOeGEE5g9ezaw6skSgFdeeYUddtiB5ubmfJnJkyfT1tbG+++/vzaezAaD1veatUk6nQ6RZXpdvzexAmslniDklL29lzWR9TWZTCa/grJO0K5HFbHODIWpU6fy3nvvcf/996/Rea666ipqa2vzr8GDB/dRCzUazWaFqrDTUH7HMXjw4IAuuuqqq0Ivs/vuu3PXXXfxxBNPcMsttzBjxgwmTpxIe3t7ryZLFixYEDAScsdzxzZltL7XrA0cx+GSSy4jHq/h/PN/QCqVwvM8br3pAUa0fJnvn3ktXZ3dKKX43e9uDz2H67rceeddLF68uMfr/Otfz/PBR++XxaEZwijbmyAUEW5kLF2ygicf810TlyxZwqGHfpWGhibuvfc+wM+SdMaUyxg58GD+cPvfUErR1ZXkgrOvY0TLl/n1DX/E8zzS6QyX/+QWtmo6kGsvvx3HcXBdl+uv+T0j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sRx00EF8/etfrdVVsG0by7L4z/+8kHnz5o3tAg2GHQij78eGMRQMBsO4xGQ9Mmws8+bN47HH/sXpp38MgM9+9iyeeOJRHn/8Ec4++/MAnHrqKTz++MPsu+++kb6TJ0/mb3+7ie99779wHIc3v/nNPP30Exx99JubTzMihBBcdNE3ueOO25g6dSozZszgnnvu5GtfOzeSZtVgGK8YfT82TNYjg8GwHbLxWY9sa+KYsx5J1Teus2BsLbalrEfNDAwM1LISbUgWR/V6NtUDfalUwrIsUqnUJjmewbA12VRZj8ai842+NzEKBoNh3KJhTKtF2/XaimEzEWcQjMRIACqLXZsOE5NgMMQxFp1v9L1xPTIYDAaDYSNYtmwZF1xwIT09PVt7KKPib3+7mUsvvexVg7EXL17Mt799CaVSaQuNzGAwbCsYQ8FgMIxLTDCzYVPw+9//H/Pm7c83v3kRc+fuy9/+dvPWHtKrUi6XOeusz3HCCW/nrLM+x1FHHc2KFSta2iml+Pa3L2HhwsM599yvccABC3jqqae2wogNho3H6PuxsdkMhZdffpnTTz+dXXfdlUwmw2te8xrOP/98PM+LtHviiSc4/PDDSafTzJ49m0suuWRzDclgMBhqmGBmw8Zy8cXf4X3v+yD5fB4IYw1OOOHtXHrpZVt5ZMPj+z4HH3wol19+RU12//0PMG/efjz//PORtu997/s599yv1XYcli5dyvz5b+DOO+/aomM2GDYFRt+Pjc1mKDz77LMopfjJT37CkiVL+P73v88VV1zBV7/61VqboaEhjjnmGHbZZRcefvhh/uu//osLLriAn/70p5trWAaDwVBBbcTLYIAbb7wJgGpOkGqV5b/+ddvdVVi7di2PP/5EpCJ0EAQMDg7x4IMPRdo2745IKZFScscdd26JoRoMmxij78fCZgtmPu644zjuuONq73fbbTeee+45Lr/8cr773e8C8Otf/xrP87jyyitJJpPss88+PPbYY3zve9/jE5/4xOYamsFgMIBWjClT2vadKM5g2CiqNSMMhu2Osej8raTvb7rpJs455xyUUnz5y1/m4x//+FYZB2zhrEeDg4NMnDix9v7+++/niCOOIJlM1mTHHnss3/nOd+jv72fChAktx3BdF9d1a++HhoY276ANBsMOSbilPHpDQZssGFsMo++Hx/O8yNxpMBg2zFh0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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln\n", + "# FROM: https://matplotlib.org/stable/gallery/statistics/hexbin_demo.html#sphx-glr-gallery-statistics-hexbin-demo-py\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "# Fixing random state for reproducibility\n", + "np.random.seed(19680801)\n", + "\n", + "n = 100_000\n", + "x = np.random.standard_normal(n)\n", + "y = 2.0 + 3.0 * x + 4.0 * np.random.standard_normal(n)\n", + "xlim = x.min(), x.max()\n", + "ylim = y.min(), y.max()\n", + "\n", + "fig, (ax0, ax1) = plt.subplots(ncols=2, sharey=True, figsize=(9, 4))\n", + "\n", + "hb = ax0.hexbin(x, y, gridsize=50, cmap='inferno')\n", + "ax0.set(xlim=xlim, ylim=ylim)\n", + "ax0.set_title(\"Hexagon binning\")\n", + "cb = fig.colorbar(hb, ax=ax0, label='counts')\n", + "\n", + "hb = ax1.hexbin(x, y, gridsize=50, bins='log', cmap='inferno')\n", + "ax1.set(xlim=xlim, ylim=ylim)\n", + "ax1.set_title(\"With a log color scale\")\n", + "cb = fig.colorbar(hb, ax=ax1, label='counts')" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "5da54b5c-6341-403d-9001-83565b50a187", + "metadata": {}, + "outputs": [], + "source": [ + "# Note that matplotlib has not been importad in global scope\n", + "assert 'matplotlib' not in globals()" + ] + }, + { + "cell_type": "markdown", + "id": "014bd19e-7d52-48a1-afe0-34cee14cbf0a", + "metadata": {}, + "source": [ + "#### Data transformation testing\n", + "\n", + "Test complex pandas pipelines without keeping intermediate results." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "b6dca02a-7f88-4de1-9488-708166b04daf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
valuessquaredlognormalized
0110.000000-1.091089
1240.693147-0.872872
2391.098612-0.654654
35251.609438-0.218218
47491.9459100.218218
5111212.3978951.091089
6131692.5649491.527525
\n", + "
" + ], + "text/plain": [ + " values squared log normalized\n", + "0 1 1 0.000000 -1.091089\n", + "1 2 4 0.693147 -0.872872\n", + "2 3 9 1.098612 -0.654654\n", + "3 5 25 1.609438 -0.218218\n", + "4 7 49 1.945910 0.218218\n", + "5 11 121 2.397895 1.091089\n", + "6 13 169 2.564949 1.527525" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%testcell\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "df = pd.DataFrame({'values': global_list})\n", + "transformed = (df\n", + " .assign(squared=lambda x: x['values']**2)\n", + " .assign(log=lambda x: np.log(x['values']))\n", + " .assign(normalized=lambda x: (x['values'] - x['values'].mean()) / x['values'].std())\n", + ")\n", + "transformed # df and transformed don't leak" + ] + }, + { + "cell_type": "markdown", + "id": "e2ec685e-3140-4802-82e3-fd793b731068", + "metadata": {}, + "source": [ + "#### Statistical analysis sandbox\n", + "\n", + "Run complex analyses without polluting namespace." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "06d2a37f-e1ec-485f-b925-9f89551faf1c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "mean 50.270582\n", + "median 50.717514\n", + "std 9.941437\n", + "q25 43.304636\n", + "q75 57.991124\n", + "dtype: float64" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%testcell\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "data = np.random.normal(loc=50, scale=10, size=100)\n", + "stats = {\n", + " 'mean': np.mean(data),\n", + " 'median': np.median(data),\n", + " 'std': np.std(data),\n", + " 'q25': np.percentile(data, 25),\n", + " 'q75': np.percentile(data, 75)\n", + "}\n", + "pd.Series(stats) # data and stats don't persist" + ] + }, + { + "cell_type": "markdown", + "id": "b3cae828-2abb-48a1-ae87-08afe2bb1cd8", + "metadata": {}, + "source": [ + "### Skip feature" + ] + }, + { + "cell_type": "markdown", + "id": "7a7431c0-4644-4cdc-91ae-05c78d0e0700", + "metadata": {}, + "source": [ + "#### A/B testing approaches\n", + "\n", + "Toggle between different implementations without deleting code." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "b2df1a3d-c95a-46d5-a108-5420f5466ae0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Approach A result: 6.0\n" + ] + } + ], + "source": [ + "%%testcell\n", + "import numpy as np\n", + "# Approach A: using numpy\n", + "result = np.array(global_list).mean()\n", + "print(f\"Approach A result: {result}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "968a250d-6d4c-4fbe-9d9e-ebed1f63d81b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " This cell has been skipped\n", + "
\n", + " " + ], + "text/plain": [ + "ℹ️ This cell has been skipped" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcell skip\n", + "# Approach B: pure python (currently disabled)\n", + "result = sum(global_list) / len(global_list)\n", + "print(f\"Approach B result: {result}\")" + ] + }, + { + "cell_type": "markdown", + "id": "70808ee6-6100-44b8-acbd-77bddcde12b7", + "metadata": {}, + "source": [ + "#### Disable expensive operations\n", + "\n", + "Skip time-consuming cells during development iteration." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "268381af-c478-43e4-b527-2e3a31f4f948", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " This cell has been skipped\n", + "
\n", + " " + ], + "text/plain": [ + "ℹ️ This cell has been skipped" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcell skip\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# Expensive simulation - skip while working on other parts\n", + "data = np.random.randn(1000000)\n", + "for i in range(100):\n", + " data = data * np.random.random() + np.random.randn(1000000)\n", + "plt.hist(data, bins=50)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "78b07769-b64e-4d3b-8ffa-27d05ef7742f", + "metadata": {}, + "source": [ + "#### Conditional cell execution with global_skip\n", + "\n", + "Disable all testcell cells at once for production runs." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "a80e5967-dd0f-44a6-a868-8b1ab065436f", + "metadata": {}, + "outputs": [], + "source": [ + "# Enable this to skip all %%testcell cells\n", + "testcell.global_skip = True" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "5c15ee09-4f83-41c9-bfe4-07ed8d9857c6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " This cell has been skipped\n", + "
\n", + " " + ], + "text/plain": [ + "ℹ️ This cell has been skipped" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcell\n", + "print(\"This won't execute when global_skip is True\")" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "4cf1f579-9766-4c95-9b03-c8b10b4774c6", + "metadata": {}, + "outputs": [], + "source": [ + "# Reset for other examples\n", + "testcell.global_skip = False" + ] + }, + { + "cell_type": "markdown", + "id": "9d08b709-bfa4-4d0d-9ad0-7117b0b0a0aa", + "metadata": {}, + "source": [ + "#### Iterative development in Colab/Modal\n", + "\n", + "Skip cells in platforms without native \"Raw cell\" type." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "27c3c0f1-74bd-47b5-8ec6-a0afc62f9180", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " This cell has been skipped\n", + "
\n", + " " + ], + "text/plain": [ + "ℹ️ This cell has been skipped" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcell skip\n", + "# Work-in-progress code that's not ready yet\n", + "# Colab/Kaggle/Modal don't have \"Raw\" cells, so this is perfect\n", + "import pandas as pd\n", + "experimental_feature = pd.DataFrame(global_list)\n", + "# TODO: finish this later" + ] + }, + { + "cell_type": "markdown", + "id": "baa724bf-83c4-4be2-a4ce-93a4d6a64e93", + "metadata": {}, + "source": [ + "### Complete isolation (noglobals/testcelln)" + ] + }, + { + "cell_type": "markdown", + "id": "624c22eb-1c65-4400-b362-4471aa80330b", + "metadata": {}, + "source": [ + "#### Pure function testing\n", + "\n", + "Verify functions work without global dependencies." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "9a0acc06-e74e-433c-a0c3-2c7c68c15ff5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'mean': np.float64(3.0),\n", + " 'std': np.float64(1.4142135623730951),\n", + " 'sum': np.int64(15)}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln\n", + "import numpy as np\n", + "\n", + "# Define and test a pure function\n", + "def calculate_stats(values):\n", + " return {\n", + " 'mean': np.mean(values),\n", + " 'std': np.std(values),\n", + " 'sum': np.sum(values)\n", + " }\n", + "\n", + "# Test with local data\n", + "calculate_stats([1, 2, 3, 4, 5])" + ] + }, + { + "cell_type": "markdown", + "id": "0e3c4c1c-970d-4ca0-aed1-c61bde89041a", + "metadata": {}, + "source": [ + "#### Detect hidden dependencies\n", + "\n", + "Catch when code accidentally relies on globals." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "8fd0452a-e7b9-442a-a6f8-3c2f144b5970", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Caught dependency: name 'global_list' is not defined\n" + ] + } + ], + "source": [ + "%%testcelln\n", + "import pandas as pd\n", + "\n", + "# This will fail - exposes dependency on global_list\n", + "def process_data():\n", + " return pd.Series(global_list).describe()\n", + "\n", + "try:\n", + " process_data()\n", + "except NameError as e:\n", + " print(f\"Caught dependency: {e}\")" + ] + }, + { + "cell_type": "markdown", + "id": "eb90f0ac-75d4-48f6-a5cc-5bf563d4360b", + "metadata": {}, + "source": [ + "#### Clean slate execution\n", + "\n", + "Run code with only `__builtins__` available." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "08766664-4eed-400f-814c-15595676dc7e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Only available: ['__builtins__']\n", + "Can use built-ins like sum: 6\n" + ] + } + ], + "source": [ + "%%testcelln\n", + "# Verify truly isolated environment\n", + "available = list(globals().keys())\n", + "print(f\"Only available: {available}\")\n", + "print(f\"Can use built-ins like sum: {sum([1,2,3])}\")" + ] + }, + { + "cell_type": "markdown", + "id": "3b98ae8c-239e-4e50-8ce3-ca54b772a1bb", + "metadata": {}, + "source": [ + "#### Reproducibility verification\n", + "\n", + "Ensure code doesn't rely on notebook state." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "453047fa-960a-43ff-a0e3-ea5933c0ca2e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
valuesnormalized
01-1.091089
12-0.872872
23-0.654654
35-0.218218
470.218218
5111.091089
6131.527525
\n", + "
" + ], + "text/plain": [ + " values normalized\n", + "0 1 -1.091089\n", + "1 2 -0.872872\n", + "2 3 -0.654654\n", + "3 5 -0.218218\n", + "4 7 0.218218\n", + "5 11 1.091089\n", + "6 13 1.527525" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "# Self-contained analysis - no hidden dependencies\n", + "data = [1, 2, 3, 5, 7, 11, 13]\n", + "df = pd.DataFrame({'values': data})\n", + "df['normalized'] = (df['values'] - df['values'].mean()) / df['values'].std()\n", + "df" + ] + }, + { + "cell_type": "markdown", + "id": "b79652f7-9bd2-424f-87a8-a5ec3dd7965f", + "metadata": {}, + "source": [ + "### (input)->(output) syntax" + ] + }, + { + "cell_type": "markdown", + "id": "b2284441-3b68-4c61-a5c2-fcd7f40340e2", + "metadata": {}, + "source": [ + "#### Controlled input to isolated cell\n", + "\n", + "Pass specific variables to noglobals context without full access." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "664f9d8f-a295-4773-80d0-8a705bac4127", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.float64(54.0)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln (global_list)\n", + "import numpy as np\n", + "\n", + "# Only global_list is available\n", + "squared = [x**2 for x in global_list]\n", + "np.mean(squared)" + ] + }, + { + "cell_type": "markdown", + "id": "847d6fc1-a04e-401b-89e1-9b84d41c62b5", + "metadata": {}, + "source": [ + "#### Safe experimentation with expensive state\n", + "\n", + "Protect important objects while experimenting - pass them in, don't leak experiments out." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "23600f42-530a-4a89-aed1-896201154723", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "expensive_model = np.random.randn(1000, 1000) # Pretend this took hours to compute" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "714abe60-955c-4c3f-8e61-d6ef86ec54eb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test prediction mean: 0.915797933814531\n" + ] + } + ], + "source": [ + "%%testcelln (expensive_model)\n", + "import numpy as np\n", + "\n", + "# Experiment safely - expensive_model is read-only here\n", + "test_input = np.random.randn(1000)\n", + "prediction = expensive_model @ test_input\n", + "temp_analysis = prediction.mean()\n", + "print(f\"Test prediction mean: {temp_analysis}\")\n", + "# temp_analysis and test_input don't leak" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "1b3ef669-2843-4f30-939c-e7cde0aa1ba2", + "metadata": {}, + "outputs": [], + "source": [ + "# Cleanup\n", + "del expensive_model " + ] + }, + { + "cell_type": "markdown", + "id": "0753b824-78a1-4b18-bf26-a14ac675372b", + "metadata": {}, + "source": [ + "#### Selective output saving\n", + "\n", + "Save only specific results back to globals." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "b3317933-4eef-4ae2-99e8-3d2b833c2798", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
valuessquared
011
124
239
3525
4749
511121
613169
\n", + "
" + ], + "text/plain": [ + " values squared\n", + "0 1 1\n", + "1 2 4\n", + "2 3 9\n", + "3 5 25\n", + "4 7 49\n", + "5 11 121\n", + "6 13 169" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln (global_list)->(final_result, summary_stats)\n", + "import pandas as pd\n", + "\n", + "# Complex processing with many intermediate variables\n", + "df = pd.DataFrame({'values': global_list})\n", + "df['squared'] = df['values'] ** 2\n", + "df['cubed'] = df['values'] ** 3\n", + "intermediate = df.describe()\n", + "\n", + "# Only these two are saved to globals\n", + "final_result = df[['values', 'squared']].copy()\n", + "summary_stats = {'mean': df['values'].mean(), 'max': df['values'].max()}\n", + "\n", + "final_result" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "0edab722-39b9-45d8-a8cc-f428153d0617", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final_result exists: True\n", + "summary_stats exists: True\n", + "df exists: False\n" + ] + } + ], + "source": [ + "# Verify only selected outputs exist\n", + "print(f\"final_result exists: {'final_result' in globals()}\")\n", + "print(f\"summary_stats exists: {'summary_stats' in globals()}\")\n", + "print(f\"df exists: {'df' in globals()}\") # Should be False\n", + "\n", + "# Cleanup\n", + "del final_result, summary_stats" + ] + }, + { + "cell_type": "markdown", + "id": "8424c0c5-7e6e-4102-a9dc-6dec90af9caf", + "metadata": {}, + "source": [ + "### Advanced scenarios" + ] + }, + { + "cell_type": "markdown", + "id": "769b2fef-43b1-42e0-abe8-a8f11a02fce4", + "metadata": {}, + "source": [ + "#### Benchmark alternatives without variable collision\n", + "\n", + "Compare implementations safely - variables from different approaches don't collide, ensuring fair comparison." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "a12daff1-fbac-4fcc-b084-1eb6165e8b8c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Approach 1 result: 10.242640687119284\n" + ] + } + ], + "source": [ + "%%testcell\n", + "import numpy as np\n", + "\n", + "# Approach 1: using numpy operations\n", + "data = np.array(global_list)\n", + "result = data.mean() + data.std()\n", + "print(f\"Approach 1 result: {result}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "fd32369b-8b31-4ee8-a4c7-ccfec19a59c9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Approach 2 result: 10.582575694955839\n" + ] + } + ], + "source": [ + "%%testcell\n", + "import numpy as np\n", + "\n", + "# Approach 2: slightly different calculation\n", + "# 'data' and 'result' from previous cell don't interfere\n", + "data = np.array(global_list)\n", + "result = np.mean(data) + np.std(data, ddof=1) # Using sample std\n", + "print(f\"Approach 2 result: {result}\")" + ] + }, + { + "cell_type": "markdown", + "id": "83df0f97-6ecd-4588-bd18-cd506f2b1367", + "metadata": {}, + "source": [ + "#### Complex fixture extraction with complete isolation\n", + "\n", + "Test in complete isolation, but extract test results to main context for later use." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "92f7f66f-36b0-4058-b62a-d80b17265823", + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "```python\n", + "### BEGIN\n", + "def _test_cell_():\n", + "\tglobal test_suite\n", + "\timport pandas as pd\n", + "\timport numpy as np\n", + "\t\n", + "\t# Create test fixtures in isolation - no access to any globals\n", + "\ttest_data = pd.DataFrame({\n", + "\t 'values': [1, 2, 3, 5, 7, 11, 13],\n", + "\t 'doubled': [2, 4, 6, 10, 14, 22, 26]\n", + "\t})\n", + "\t\n", + "\ttest_suite = {\n", + "\t 'data': test_data,\n", + "\t 'validators': [lambda x: x > 0, lambda x: x < 100],\n", + "\t 'summary': test_data.describe().to_dict()\n", + "\t}\n", + "\t\n", + "\treturn test_suite['data'].head() # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "if _ is not None: display(_)\n", + "### END\n", + "```" + ], + "text/plain": [ + "### BEGIN\n", + "def _test_cell_():\n", + "\tglobal test_suite\n", + "\timport pandas as pd\n", + "\timport numpy as np\n", + "\t\n", + "\t# Create test fixtures in isolation - no access to any globals\n", + "\ttest_data = pd.DataFrame({\n", + "\t 'values': [1, 2, 3, 5, 7, 11, 13],\n", + "\t 'doubled': [2, 4, 6, 10, 14, 22, 26]\n", + "\t})\n", + "\t\n", + "\ttest_suite = {\n", + "\t 'data': test_data,\n", + "\t 'validators': [lambda x: x > 0, lambda x: x < 100],\n", + "\t 'summary': test_data.describe().to_dict()\n", + "\t}\n", + "\t\n", + "\treturn test_suite['data'].head() # %%testcell\n", + "try:\n", + "\t_ = _test_cell_()\n", + "finally:\n", + "\tdel _test_cell_\n", + "if _ is not None: display(_)\n", + "### END" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
valuesdoubled
012
124
236
3510
4714
\n", + "
" + ], + "text/plain": [ + " values doubled\n", + "0 1 2\n", + "1 2 4\n", + "2 3 6\n", + "3 5 10\n", + "4 7 14" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/markdown": [ + "```python\n", + "### GLOBALS UPDATE CODE:\n", + "global test_suite; test_suite=locals()[\"test_suite\"]\n", + "###\n", + "```" + ], + "text/plain": [ + "### GLOBALS UPDATE CODE:\n", + "global test_suite; test_suite=locals()[\"test_suite\"]\n", + "###" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln ->(test_suite) debug\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "# Create test fixtures in isolation - no access to any globals\n", + "test_data = pd.DataFrame({\n", + " 'values': [1, 2, 3, 5, 7, 11, 13],\n", + " 'doubled': [2, 4, 6, 10, 14, 22, 26]\n", + "})\n", + "\n", + "test_suite = {\n", + " 'data': test_data,\n", + " 'validators': [lambda x: x > 0, lambda x: x < 100],\n", + " 'summary': test_data.describe().to_dict()\n", + "}\n", + "\n", + "test_suite['data'].head()" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "d8113ab2-096a-4c18-a814-a6cdd6b5f6b4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test suite keys: dict_keys(['data', 'validators', 'summary'])\n" + ] + } + ], + "source": [ + "# test_suite now available in main context\n", + "print(f\"Test suite keys: {test_suite.keys()}\")\n", + "del test_suite # Cleanup" + ] + }, + { + "cell_type": "markdown", + "id": "37d85e36-ead8-40b2-bfe2-2b4ec0edcc72", + "metadata": {}, + "source": [ + "#### Pipeline validation during refactoring\n", + "\n", + "Test two pipeline versions produce identical results - crucial when refactoring to ensure behavior doesn't change." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "88ec22c2-27db-4f7b-be45-4218465f68c0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
valuesdoublednormalized
012-1.091089
124-0.872872
236-0.654654
3510-0.218218
47140.218218
511221.091089
613261.527525
\n", + "
" + ], + "text/plain": [ + " values doubled normalized\n", + "0 1 2 -1.091089\n", + "1 2 4 -0.872872\n", + "2 3 6 -0.654654\n", + "3 5 10 -0.218218\n", + "4 7 14 0.218218\n", + "5 11 22 1.091089\n", + "6 13 26 1.527525" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln (global_list)->(pipeline_v1)\n", + "import pandas as pd\n", + "\n", + "def pipeline_v1(data):\n", + " df = pd.DataFrame({'values': data})\n", + " df['doubled'] = df['values'] * 2\n", + " df['normalized'] = (df['doubled'] - df['doubled'].mean()) / df['doubled'].std()\n", + " return df\n", + "\n", + "pipeline_v1(global_list)" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "c60e5102-e24b-4ff8-b90f-6710b744d967", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
valuesdoublednormalized
012-1.178511
124-0.942809
236-0.707107
3510-0.235702
47140.235702
511221.178511
613261.649916
\n", + "
" + ], + "text/plain": [ + " values doubled normalized\n", + "0 1 2 -1.178511\n", + "1 2 4 -0.942809\n", + "2 3 6 -0.707107\n", + "3 5 10 -0.235702\n", + "4 7 14 0.235702\n", + "5 11 22 1.178511\n", + "6 13 26 1.649916" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%testcelln (global_list)->(pipeline_v2)\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "def pipeline_v2(data):\n", + " # Refactored version - more efficient\n", + " df = pd.DataFrame({'values': data})\n", + " doubled = np.array(df['values']) * 2\n", + " df['doubled'] = doubled\n", + " df['normalized'] = (doubled - doubled.mean()) / doubled.std()\n", + " return df\n", + "\n", + "pipeline_v2(global_list)" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "ddfa72c5-f094-4013-b3dc-8efe26ead105", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 valuesdoublednormalized
0000.087422
1000.069937
2000.052453
3000.017484
400-0.017484
500-0.087422
600-0.122391
\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%testcell\n", + "# Inspect differences\n", + "import pandas as pd\n", + "(pipeline_v1(global_list) - pipeline_v2(global_list)).style.background_gradient(cmap='viridis', subset=['normalized'])" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "b43e6de4-c4ba-4b31-ac5b-7f118fcf45b4", + "metadata": {}, + "outputs": [], + "source": [ + "# Cleanup\n", + "del pipeline_v1, pipeline_v2" + ] + }, + { + "cell_type": "markdown", + "id": "a1afcc17-689d-4661-8645-cd8bb7a0665c", + "metadata": {}, + "source": [ + "#### Debugging global pollution and dependencies\n", + "\n", + "Same code behaves differently with testcell vs testcelln - exposes hidden dependency on global scope." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "a1a7be2c-3dec-4f73-88dd-24f5a9f0ba9d", + "metadata": {}, + "outputs": [], + "source": [ + "# Set up a variable that code might accidentally depend on\n", + "scale_factor = 10" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "1722d9ea-9b49-4101-ab9b-477594ddf00d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.float64(60.0)" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%testcell\n", + "import numpy as np\n", + "\n", + "# This works - has access to scale_factor\n", + "data = np.array(global_list) * scale_factor\n", + "data.mean()" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "3f6bdfc1-87b8-4f72-960a-657a487290ec", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "❌ Hidden dependency detected: name 'scale_factor' is not defined\n", + "Code relies on 'scale_factor' from global scope!\n" + ] + } + ], + "source": [ + "%%testcelln\n", + "import numpy as np\n", + "\n", + "# This fails - exposes the hidden dependency\n", + "try:\n", + " data = np.array([1,2,3,5,7,11,13]) * scale_factor\n", + " data.mean()\n", + "except NameError as e:\n", + " print(f\"❌ Hidden dependency detected: {e}\")\n", + " print(\"Code relies on 'scale_factor' from global scope!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "b446b0cf-4670-4089-be96-cfd2073bdd18", + "metadata": {}, + "outputs": [], + "source": [ + "# Cleanup\n", + "del scale_factor" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 626d9022a583761fbb73fd823c0b81bc3c944c96 Mon Sep 17 00:00:00 2001 From: Stefano Giomo Date: Thu, 6 Nov 2025 12:08:14 +0000 Subject: [PATCH 10/18] Updated badge example --- demo/testcell_zoo.ipynb | 217 +++++++++++++++++++++++----------------- 1 file changed, 126 insertions(+), 91 deletions(-) diff --git a/demo/testcell_zoo.ipynb b/demo/testcell_zoo.ipynb index 0795cc3..c9847b3 100644 --- a/demo/testcell_zoo.ipynb +++ b/demo/testcell_zoo.ipynb @@ -305,6 +305,41 @@ "global_function(3)" ] }, + { + "cell_type": "code", + "execution_count": 9, + "id": "2927380d-4dc1-487f-90d4-ae0ebdff18dc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + " testcell noglobals\n", + "
\n", + " " + ], + "text/plain": [ + "🟢 testcell noglobals" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This is running in an isolated environment\n" + ] + } + ], + "source": [ + "%%testcelln banner\n", + "print('This is running in an isolated environment')" + ] + }, { "cell_type": "markdown", "id": "96abea99-be60-4244-a00c-2fccd28cbd65", @@ -316,7 +351,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "c8f4ee3c-750c-4e84-a321-2a5be1d976a1", "metadata": {}, "outputs": [ @@ -338,7 +373,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "id": "fd7f6e61-b159-4d81-9b89-910fbae42c34", "metadata": {}, "outputs": [ @@ -369,7 +404,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "id": "48412b54-b2ce-4cdb-8a2e-7dac4a3a2d98", "metadata": {}, "outputs": [ @@ -390,7 +425,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "id": "bffb1ef7-4c40-4d82-8b37-e5d72e93079e", "metadata": {}, "outputs": [ @@ -420,7 +455,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "id": "008e7a4c-86c9-46da-8ddc-cfef3e482fc2", "metadata": {}, "outputs": [ @@ -442,7 +477,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "id": "e0426272-2f12-4658-ad08-eba79b63cc43", "metadata": {}, "outputs": [ @@ -470,7 +505,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "4773e185-9605-4567-bab4-1abc910f353d", "metadata": {}, "outputs": [ @@ -544,7 +579,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "94a81e1e-9bc7-46a9-b5c9-3c67327daced", "metadata": {}, "outputs": [], @@ -580,7 +615,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "1c6d0120-9c43-427b-b26d-f21f74138fb5", "metadata": {}, "outputs": [ @@ -597,7 +632,7 @@ "np.float64(58.0)" ] }, - "execution_count": 17, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -625,7 +660,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "a465106a-18f1-49b2-a012-a20f13504437", "metadata": {}, "outputs": [ @@ -662,7 +697,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "id": "e3887d3e-b47d-4abb-9a06-86a7b399f8cd", "metadata": {}, "outputs": [ @@ -702,7 +737,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "id": "cd8d15ba-ee02-43a8-90ca-7bd1a28c2ca2", "metadata": {}, "outputs": [ @@ -814,7 +849,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "id": "5f8576c0-9a7a-40bb-8b4a-647ff205aa1e", "metadata": {}, "outputs": [], @@ -843,7 +878,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "id": "f3e757d9-b253-43d9-bf67-85e051b5faba", "metadata": {}, "outputs": [ @@ -888,7 +923,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 24, "id": "5da54b5c-6341-403d-9001-83565b50a187", "metadata": {}, "outputs": [], @@ -909,7 +944,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 25, "id": "b6dca02a-7f88-4de1-9488-708166b04daf", "metadata": {}, "outputs": [ @@ -1005,7 +1040,7 @@ "6 13 169 2.564949 1.527525" ] }, - "execution_count": 24, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -1036,7 +1071,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "id": "06d2a37f-e1ec-485f-b925-9f89551faf1c", "metadata": {}, "outputs": [ @@ -1051,7 +1086,7 @@ "dtype: float64" ] }, - "execution_count": 25, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -1092,7 +1127,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 27, "id": "b2df1a3d-c95a-46d5-a108-5420f5466ae0", "metadata": {}, "outputs": [ @@ -1114,7 +1149,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 28, "id": "968a250d-6d4c-4fbe-9d9e-ebed1f63d81b", "metadata": {}, "outputs": [ @@ -1154,7 +1189,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 29, "id": "268381af-c478-43e4-b527-2e3a31f4f948", "metadata": {}, "outputs": [ @@ -1200,7 +1235,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 30, "id": "a80e5967-dd0f-44a6-a868-8b1ab065436f", "metadata": {}, "outputs": [], @@ -1211,7 +1246,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 31, "id": "5c15ee09-4f83-41c9-bfe4-07ed8d9857c6", "metadata": {}, "outputs": [ @@ -1239,7 +1274,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 32, "id": "4cf1f579-9766-4c95-9b03-c8b10b4774c6", "metadata": {}, "outputs": [], @@ -1260,7 +1295,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 33, "id": "27c3c0f1-74bd-47b5-8ec6-a0afc62f9180", "metadata": {}, "outputs": [ @@ -1310,7 +1345,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 34, "id": "9a0acc06-e74e-433c-a0c3-2c7c68c15ff5", "metadata": {}, "outputs": [ @@ -1354,7 +1389,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 35, "id": "8fd0452a-e7b9-442a-a6f8-3c2f144b5970", "metadata": {}, "outputs": [ @@ -1392,7 +1427,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 36, "id": "08766664-4eed-400f-814c-15595676dc7e", "metadata": {}, "outputs": [ @@ -1425,7 +1460,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 37, "id": "453047fa-960a-43ff-a0e3-ea5933c0ca2e", "metadata": {}, "outputs": [ @@ -1541,7 +1576,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 38, "id": "664f9d8f-a295-4773-80d0-8a705bac4127", "metadata": {}, "outputs": [ @@ -1576,7 +1611,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 39, "id": "23600f42-530a-4a89-aed1-896201154723", "metadata": {}, "outputs": [], @@ -1587,7 +1622,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 40, "id": "714abe60-955c-4c3f-8e61-d6ef86ec54eb", "metadata": {}, "outputs": [ @@ -1613,7 +1648,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 41, "id": "1b3ef669-2843-4f30-939c-e7cde0aa1ba2", "metadata": {}, "outputs": [], @@ -1634,7 +1669,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 42, "id": "b3317933-4eef-4ae2-99e8-3d2b833c2798", "metadata": {}, "outputs": [ @@ -1737,7 +1772,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 43, "id": "0edab722-39b9-45d8-a8cc-f428153d0617", "metadata": {}, "outputs": [ @@ -1781,7 +1816,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 44, "id": "a12daff1-fbac-4fcc-b084-1eb6165e8b8c", "metadata": {}, "outputs": [ @@ -1805,7 +1840,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 45, "id": "fd32369b-8b31-4ee8-a4c7-ccfec19a59c9", "metadata": {}, "outputs": [ @@ -1840,7 +1875,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 46, "id": "92f7f66f-36b0-4058-b62a-d80b17265823", "metadata": {}, "outputs": [ @@ -2014,7 +2049,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 47, "id": "d8113ab2-096a-4c18-a814-a6cdd6b5f6b4", "metadata": {}, "outputs": [ @@ -2044,7 +2079,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 48, "id": "88ec22c2-27db-4f7b-be45-4218465f68c0", "metadata": {}, "outputs": [ @@ -2151,7 +2186,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 49, "id": "c60e5102-e24b-4ff8-b90f-6710b744d967", "metadata": {}, "outputs": [ @@ -2261,7 +2296,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 50, "id": "ddfa72c5-f094-4013-b3dc-8efe26ead105", "metadata": {}, "outputs": [ @@ -2269,95 +2304,95 @@ "data": { "text/html": [ "\n", - "\n", + "
\n", " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", "
 valuesdoublednormalizedvaluesdoublednormalized
0000.0874220000.087422
1000.0699371000.069937
2000.0524532000.052453
3000.0174843000.017484
400-0.017484400-0.017484
500-0.087422500-0.087422
600-0.122391600-0.122391
\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 49, + "execution_count": 50, "metadata": {}, "output_type": "execute_result" } @@ -2371,7 +2406,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 51, "id": "b43e6de4-c4ba-4b31-ac5b-7f118fcf45b4", "metadata": {}, "outputs": [], @@ -2392,7 +2427,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 52, "id": "a1a7be2c-3dec-4f73-88dd-24f5a9f0ba9d", "metadata": {}, "outputs": [], @@ -2403,7 +2438,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 53, "id": "1722d9ea-9b49-4101-ab9b-477594ddf00d", "metadata": {}, "outputs": [ @@ -2413,7 +2448,7 @@ "np.float64(60.0)" ] }, - "execution_count": 52, + "execution_count": 53, "metadata": {}, "output_type": "execute_result" } @@ -2429,7 +2464,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 54, "id": "3f6bdfc1-87b8-4f72-960a-657a487290ec", "metadata": {}, "outputs": [ @@ -2457,7 +2492,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 55, "id": "b446b0cf-4670-4089-be96-cfd2073bdd18", "metadata": {}, "outputs": [], From 95e5509a40397bc09ef4c31dcae1620433efb7fc Mon Sep 17 00:00:00 2001 From: Stefano Giomo Date: Sat, 8 Nov 2025 11:58:32 +0000 Subject: [PATCH 11/18] Improved test coverage on separate_args_and_inout --- nbs/01a_arguments.ipynb | 44 +++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/nbs/01a_arguments.ipynb b/nbs/01a_arguments.ipynb index 6d29644..72c07c8 100644 --- a/nbs/01a_arguments.ipynb +++ b/nbs/01a_arguments.ipynb @@ -273,31 +273,33 @@ "metadata": {}, "outputs": [], "source": [ - "test_eq(separate_args_and_inout(None),None)\n", + "test_eq(separate_args_and_inout(None), None) # None input → None output\n", "\n", - "test_eq(separate_args_and_inout(''),['',None])\n", - "test_eq(separate_args_and_inout('verbose'),['verbose',None])\n", - "test_eq(separate_args_and_inout('dryrun verbose'),['dryrun verbose',None])\n", - "test_eq(separate_args_and_inout('(a,b)->(c,d)'),['','(a,b)->(c,d)'])\n", - "test_eq(separate_args_and_inout('(a,b)'),['','(a,b)'])\n", - "test_eq(separate_args_and_inout('->(c,d)'),['','->(c,d)'])\n", + "test_eq(separate_args_and_inout(''), ['', None]) # Empty string → empty args, no in/out\n", + "test_eq(separate_args_and_inout('verbose'), ['verbose', None]) # Only args, no in/out\n", + "test_eq(separate_args_and_inout('dryrun verbose'), ['dryrun verbose', None]) # Multiple args, no in/out\n", + "test_eq(separate_args_and_inout('(a,b)->(c,d)'), ['', '(a,b)->(c,d)']) # Pure in/out spec, no args\n", + "test_eq(separate_args_and_inout('(a,b)'), ['', '(a,b)']) # Only input tuple, no args\n", + "test_eq(separate_args_and_inout('->(c,d)'), ['', '->(c,d)']) # Only output tuple, no args\n", "\n", - "test_eq(separate_args_and_inout('dryrun verbose (a,b)'),['dryrun verbose ','(a,b)'])\n", - "test_eq(separate_args_and_inout('dryrun verbose (a,b) -> (c,d) '),['dryrun verbose ','(a,b) -> (c,d)'])\n", - "test_eq(separate_args_and_inout('dryrun verbose () -> (c,d) '),['dryrun verbose ','() -> (c,d)'])\n", - "test_eq(separate_args_and_inout('dryrun verbose (a,b) -> ()'),['dryrun verbose ','(a,b) -> ()'])\n", - "test_eq(separate_args_and_inout('dryrun verbose (a,b) '),['dryrun verbose ','(a,b)'])\n", + "test_eq(separate_args_and_inout('dryrun verbose (a,b)'), ['dryrun verbose ', '(a,b)']) # Args + input tuple\n", + "test_eq(separate_args_and_inout('dryrun verbose (a,b) -> (c,d) '), ['dryrun verbose ', '(a,b) -> (c,d)']) # Args + in/out\n", + "test_eq(separate_args_and_inout('dryrun verbose () -> (c,d) '), ['dryrun verbose ', '() -> (c,d)']) # Args + empty input tuple\n", + "test_eq(separate_args_and_inout('dryrun verbose (a,b) -> ()'), ['dryrun verbose ', '(a,b) -> ()']) # Args + empty output tuple\n", + "test_eq(separate_args_and_inout('dryrun verbose (a,b) '), ['dryrun verbose ', '(a,b)']) # Args + input tuple, trailing spaces\n", "\n", - "test_eq(separate_args_and_inout('dryrun (a,b) verbose '),['dryrun verbose ','(a,b)'])\n", - "test_eq(separate_args_and_inout('dryrun (a,b) -> (c,d) verbose'),['dryrun verbose','(a,b) -> (c,d)'])\n", - "test_eq(separate_args_and_inout('dryrun () -> (c,d) verbose'),['dryrun verbose','() -> (c,d)'])\n", - "test_eq(separate_args_and_inout('dryrun () -> (c,d)verbose'),['dryrun verbose','() -> (c,d)'])\n", - "test_eq(separate_args_and_inout('dryrun() -> (c,d) verbose'),['dryrun verbose','() -> (c,d)'])\n", - "test_eq(separate_args_and_inout('dryrun (a,b)->() verbose'),['dryrun verbose','(a,b)->()'])\n", - "test_eq(separate_args_and_inout('dryrun (a, b) verbose'),['dryrun verbose','(a, b)'])\n", + "test_eq(separate_args_and_inout('dryrun (a,b) verbose '), ['dryrun verbose ', '(a,b)']) # In/out tuple between args\n", + "test_eq(separate_args_and_inout('dryrun (a,b) -> (c,d) verbose'), ['dryrun verbose', '(a,b) -> (c,d)']) # In/out in middle of args\n", + "test_eq(separate_args_and_inout('dryrun () -> (c,d) verbose'), ['dryrun verbose', '() -> (c,d)']) # Empty input tuple\n", + "test_eq(separate_args_and_inout('dryrun () -> (c,d)verbose'), ['dryrun verbose', '() -> (c,d)']) # No space before verbose\n", + "test_eq(separate_args_and_inout('dryrun() -> (c,d) verbose'), ['dryrun verbose', '() -> (c,d)']) # No space before ()\n", + "test_eq(separate_args_and_inout('dryrun (a,b)->() verbose'), ['dryrun verbose', '(a,b)->()']) # Compact arrow, spacing preserved\n", + "test_eq(separate_args_and_inout('dryrun (a, b) verbose'), ['dryrun verbose', '(a, b)']) # Tuple spacing preserved\n", "\n", - "test_eq(separate_args_and_inout('dryrun -> (c,d)verbose'),['dryrun verbose','-> (c,d)'])\n", - "test_eq(separate_args_and_inout('dryrun (c,d)->verbose'),['dryrun verbose','(c,d)->'])" + "test_eq(separate_args_and_inout('dryrun -> (c,d)verbose'), ['dryrun verbose', '-> (c,d)']) # Output tuple glued to arg\n", + "test_eq(separate_args_and_inout('dryrun (c,d)->verbose'), ['dryrun verbose', '(c,d)->']) # Input tuple glued to arg\n", + "\n", + "test_eq(separate_args_and_inout('dryrun (incomplete'), ['dryrun (incomplete', None]) # Unclosed parenthesis → ignore as in/out" ] }, { From 3e86825f34dc4e15e66d2ce542e8a92562f80a9f Mon Sep 17 00:00:00 2001 From: Stefano Giomo Date: Sat, 8 Nov 2025 12:07:39 +0000 Subject: [PATCH 12/18] Protect against html injection --- nbs/02_testcell.ipynb | 131 +++++++++++++++++++++++------------------- testcell/__init__.py | 57 +++++++++--------- 2 files changed, 102 insertions(+), 86 deletions(-) diff --git a/nbs/02_testcell.ipynb b/nbs/02_testcell.ipynb index 9d2db37..f8aeda5 100644 --- a/nbs/02_testcell.ipynb +++ b/nbs/02_testcell.ipynb @@ -88,6 +88,16 @@ "## Support classes " ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Used only in tests\n", + "from fastcore.test import *" + ] + }, { "cell_type": "code", "execution_count": null, @@ -95,25 +105,39 @@ "outputs": [], "source": [ "#| export\n", - "# Temporary class to deal with different display options (jupyter or console)\n", + "import html\n", + "\n", "class MessageBox:\n", - " def __init__(self,data,*,background_color,text_color,emoji=None):\n", - " self.data = data\n", - " self.background_color = background_color\n", - " self.text_color = text_color\n", - " self.emoji = emoji\n", + " def __init__(self,data,*,background_color,text_color,emoji=None):\n", + " self.data = data\n", + " self.background_color = background_color\n", + " self.text_color = text_color\n", + " self.emoji = emoji\n", "\n", - " def _repr_html_(self):\n", - " return f\"\"\"\n", - "
\n", - " {self.data}\n", - "
\n", - " \"\"\"\n", + " def _repr_html_(self):\n", + " # Escape all user-controllable values\n", + " safe_bg = html.escape(str(self.background_color), quote=True)\n", + " safe_text = html.escape(str(self.text_color), quote=True)\n", + " safe_data = html.escape(str(self.data))\n", "\n", - " def __repr__(self): \n", - " text = self.data\n", - " if self.emoji is not None: text = self.emoji + \" \" + text\n", - " return text" + " return f\"\"\"\n", + "
\n", + " {safe_data}\n", + "
\n", + " \"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# test against html injection\n", + "mb = MessageBox('', background_color='red', text_color='black')\n", + "test_eq('<script>' in mb._repr_html_(), True) # Escaped = GOOD\n", + "test_eq('