Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
285 changes: 164 additions & 121 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,224 +3,267 @@

<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

**TL;DR**: `%%testcell` prevents your testing cells from affecting the
global namespace.
One good thing about working in Jupyter notebooks is that they make it
easy to quickly test a bit of code by evaluating it in notebook cell.
But one bad thing is that the *definitions* resulting from that
evaluation hang around afterwards, when all you wanted was just to test
that one bit of code.

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.
`%%testcell` is a simple simple solution to that problem. It lets you
execute notebook cells in isolation. Test code, try snippets, and
experiment freely: no variables, functions, classes, or imports are left
behind. This helps to keep your namespace clean, so that leftover
symbols do not confuse later work.

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.

**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)

[![](https://kaggle.com/static/images/open-in-kaggle.svg)](https://www.kaggle.com/artste/introducing-testcell)
[![](https://modal-cdn.com/open-in-modal.svg)](https://modal.com/notebooks/new/https://github.com/artste/testcell/blob/main/demo/testcell_demo.ipynb)

## 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

First import `testcell`:

``` python
import testcell
```

just import it with `import testcell` and then use the `%%testcell` cell
magic.
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` keywork will print which code will be executed.
## How it works

NOTE: The actual cell code is enclosed within `BEGIN` and `END` comment
blocks for improved readability.
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.

Use `verbose` to see the generated wrapper code:

``` python
%%testcell verbose
a = "'a' is not polluting global scope"
a
result = "isolated execution"
result
```

"'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:

``` python
%%testcell dryrun
a = "'a' is not polluting global scope"
a
### BEGIN
def _test_cell_():
#| echo: false
result = "isolated execution"
return result # %%testcell
try:
_ = _test_cell_()
finally:
del _test_cell_
_ # This will be added to global scope
### 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.
'isolated execution'

## Suppressing output

Like normal Jupyter cells, add a semicolon `;` to the last statement to
suppress display:

``` python
%%testcell verbose
a = "'a' is not polluting global scope"
a;
%%testcell
calculation = 42 * 2
calculation;
```

`testcell` works seamlessly with existing `print` or `display`statements
on last line:
No output is displayed, and `calculation` still doesn’t leak to globals.

## Skip execution

Skip cells without deleting code using the `skip` command.

**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')
```

'a' is not polluting global scope
<testcell.MessageBox object>

Moreover, thanks to `ast`, it properly deals with complex situations
like comments on the last line and multi lines statements
To skip all `%%testcell` cells at once (useful for production runs),
use: `testcell.global_skip = True`

## Visual marking

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"
```

("'a' is not polluting global scope", True)
<testcell.MessageBox object>

'clearly marked'

**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.

### Skip execution
Colors and emojis are fully customizable through `testcell.params`.

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
the following syntax: `testcell.global_skip=True`.
**IMPORTANT**: To enable banners for all `%%testcell` cells, use:
**`testcell.global_use_banner = True`**

<div style="background-color: #808080; padding: 3px; text-align: center; font-size: 12px; color: white;">
This cell has been skipped
</div>
&#10;
## 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 to
`__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
``` python
%%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))
```

As you can see from this last example, `%%testcelln` helps you to
identify that `my_function` refers global variable `aaa`.
{'mean': 3.0, 'min': 1, 'max': 5}

**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.
`calculate_stats` now **exists in globals**. No test code or
intermediate variables leaked.

``` python
aaa
calculate_stats([10, 20, 30])
```

'global variable'
{'mean': 20.0, 'min': 10, 'max': 30}

``` python
%%testcell
# WARNING: this will alter the state of global variable:
globals().update({'aaa' : 'modified global variable'});
```
## Advanced parsing

Thanks to Python’s `ast` module, `%%testcell` correctly handles complex
code patterns including comments on the last line and multi-line
statements:

``` python
aaa
%%testcell verbose
result = "complex parsing"
(result,
True)
# comment on last line
```

'modified global variable'

``` python
del aaa
### BEGIN
def _test_cell_():
#| echo: false
result = "complex parsing"
return (result,
True) # %%testcell
try:
_ = _test_cell_()
finally:
del _test_cell_
_ # This will be added to global scope
### END
```

('complex parsing', True)

## Links:

- PROJECT PAGE: <https://github.com/artste/testcell>
- DOCUMENTATION: <https://artste.github.io/testcell>
- PYPI: <https://pypi.org/project/testcell>
- DETAILED DEMO:
<https://github.com/artste/testcell/blob/main/demo/testcell_demo.ipynb>
- USE CASE ZOO:
<https://github.com/artste/testcell/blob/main/demo/testcell_zoo.ipynb>
- 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:
Expand Down
Loading