Metadata-Version: 2.4
Name: nbdantic
Version: 0.1.1rc1
Requires-Dist: ansiwrap>=0.8.4
Requires-Dist: nbformat>=5.10.4
Requires-Dist: tomlkit>=0.13.0
Summary: notebook parsing minimum DSL
Requires-Python: >=3.13
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Documentation, https://github.com/ivanbelenky/nbdantic#readme
Project-URL: Homepage, https://github.com/ivanbelenky/nbdantic
Project-URL: Issues, https://github.com/ivanbelenky/nbdantic/issues
Project-URL: Repository, https://github.com/ivanbelenky/nbdantic

# nbdantic ◬

fully typed context-free grammar for jupyter notebooks. declare expected structure, validate against it.

## install

```
uv add nbdantic
```

## grammar

| production | rule | note |
|---|---|---|
| Notebook | Cell\* Sequence Cell_terminal? | |
| Sequence | (Element …)+ \| ε | `empty=True` allows ε |
| Element | Markdown \| Code \| Sequence \| Choice \| Maybe | |
| Markdown | cell\<markdown\> | terminal |
| Code | cell\<code\> | terminal |
| Choice | Element \| Element \| … | first match wins |
| Maybe | Element \| ε | |

### grammar elements

```python

type ParseElement = Markdown | Code | Sequence | Choice | Maybe

# Markdown and Code are self explanatory
# Sequence is None one or more elements (enabling nesting of structures/sequences)
# Choice lets you pick from a set of Elements (it could be arbitrary sets of nested pattenrs)
# Maybe allows for expressing Optiona<Element>
```


### usage

```python
import ast # for custom validation function

from nbdantic import Code, Markdown, Maybe, Sequence, JupyterNotebook
from nbdantic.validators import valid_python, not_empty, at_least


def only_imports(c):
    tree = ast.parse(c)
    if not all(
        isinstance(n, (ast.Import, ast.ImportFrom, ast.alias, ast.Module)) for n in ast.walk(tree)
    ):
        raise ValueError("code should only contain import statements")


class Paper(JupyterNotebook):
    structure = Sequence("root", [
        Markdown("title", validators=[not_empty]),
        Code("imports", validators=[valid_python, only_imports]),
        Maybe("abstract", Markdown("abstract_md")),
        Sequence("sections", [
            Markdown("heading"),
            Code("body", validators=[valid_python]),
        ], validators=[at_least(2)], empty=True),
    ])

result = Paper.from_file("paper.ipynb").validate()
result.raise_if_failed()
result.cells_map
```


### extra features

#### built-in validators

`nbdantic.validators` has some common checks, some of them are simple and example like, some of them may prove useful, like
- `ty_check`
- `ruff_check`
- `line_warning`


#### script runner

`nbdantic.script_runner.UVScriptRunner` spins up isolated uv environments for running code, linting, type checking, whatever you need. validators like `ruff_check` and `ty_check` use it under the hood.

```python
from nbdantic.script_runner import UVScriptRunner

with UVScriptRunner(packages=["numpy>=1.24"], python_version="3.13") as runner:
    result = runner.oneshot_python("import numpy; print(numpy.__version__)")
    print(result.stdout)
```


### notes
pep-508/440 parsing backed by [uv-pep508 & uv-pep440 crates](https://github.com/astral-sh/uv) via pyo3. Thanks uv for everything.

