Skip to content

Commit 7fc429f

Browse files
Parse (#1)
1 parent 31b887a commit 7fc429f

File tree

16 files changed

+454
-127
lines changed

16 files changed

+454
-127
lines changed

.github/workflows/check-python.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,23 @@ jobs:
4040
4141
lint:
4242
runs-on: ubuntu-latest
43+
env:
44+
ruff_args: ""
45+
python-version: '3.13'
4346
steps:
4447
- uses: actions/checkout@v4
45-
- name: Ruff
46-
uses: chartboost/ruff-action@v1
48+
- name: Set stricter linting rules for PR / `main`
49+
run: |
50+
if [[ "${{ github.event_name }}" == "pull_request" || ( "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ) ]]; then
51+
echo "ruff_args=--config ruff-main.toml" >> $GITHUB_ENV
52+
fi
53+
- name: Install uv
54+
uses: astral-sh/setup-uv@v5
55+
with:
56+
python-version: ${{ env.python-version }}
57+
- name: lint with ruff ${{ env.ruff_args }}
58+
run: |
59+
uv run --no-default-groups --group lint ruff check . ${{ env.ruff_args }}
4760
4861
type-check:
4962
env:

.github/workflows/publish-python.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
publish:
3636
environment:
3737
name: pypi
38-
url: https://pypi.org/p/ttrpg-dice
38+
url: https://pypi.org/p/pytest-ipynb2
3939
permissions:
4040
id-token: write
4141
needs: [python-checks, build]

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
## [0.0.1] - 2025-02-12
11+
12+
### Added
13+
14+
- Initial release of `pytest-ipynb2`.
15+
- Added support for running tests in Jupyter Notebooks.
16+
- Implemented `Notebook` class for parsing notebooks and extracting code and test cells.
17+
- Added GitHub Actions workflows for CI/CD, including linting, testing, type-checking, and publishing.
18+
- Added `justfile` for common development tasks.
19+
- Configured `pyproject.toml` with project metadata, dependencies, and tool configurations.

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# pytest-ipynb2
2+
3+
A pytest plugin to run tests in Jupyter Notebooks. Designed to integrate with [chmp/ipytest](https://github.com/chmp/ipytest).

docs/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Overview
2+
3+
A pytest plugin to run tests in Jupyter Notebooks. Designed to integrate with [chmp/ipytest](https://github.com/chmp/ipytest).

docs/python-api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# API
2+
3+
::: pytest_ipynb2

justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,4 @@ show-cov:
5858

5959
# serve python docs on localhost:3000
6060
docs:
61-
uv run jupyter book start --execute
61+
uv run mkdocs serve

mkdocs.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
site_name: "pytest-ipynb2"
2+
repo_url: https://github.com/MusicalNinjaDad/pytest-ipynb2
3+
repo_name: MusicalNinjaDad/pytest-ipynb2
4+
5+
watch:
6+
- pytest_ipynb2 # To update live preview when docstrings change
7+
8+
theme:
9+
name: "material"
10+
icon:
11+
logo: material/test-tube
12+
palette: # Palette toggles for auto-light-dark mode
13+
- media: "(prefers-color-scheme)"
14+
toggle:
15+
icon: material/link
16+
name: Switch to light mode
17+
- media: "(prefers-color-scheme: light)"
18+
scheme: default
19+
toggle:
20+
icon: material/toggle-switch
21+
name: Switch to dark mode
22+
- media: "(prefers-color-scheme: dark)"
23+
scheme: slate
24+
toggle:
25+
icon: material/toggle-switch-off-outline
26+
name: Switch to system preference
27+
features:
28+
- navigation.expand
29+
- navigation.path
30+
- toc.integrate
31+
- navigation.indexes
32+
33+
plugins:
34+
- search
35+
- mkdocstrings:
36+
handlers:
37+
python:
38+
options:
39+
members: true
40+
show_bases: false
41+
show_root_heading: true
42+
heading_level: 2
43+
show_root_full_path: false
44+
show_symbol_type_toc: true
45+
show_symbol_type_heading: true
46+
signature_crossrefs: true
47+
separate_signature: true
48+
show_signature_annotations: true
49+
docstring_section_style: spacy
50+
docstring_options:
51+
ignore_init_summary: true
52+
merge_init_into_class: true
53+
54+
markdown_extensions:
55+
- admonition
56+
- pymdownx.details
57+
- pymdownx.highlight
58+
- pymdownx.inlinehilite
59+
- pymdownx.snippets:
60+
url_download: true
61+
base_path: !relative $docs_dir
62+
- pymdownx.superfences
63+
- attr_list
64+
- pymdownx.emoji:
65+
emoji_index: !!python/name:material.extensions.emoji.twemoji
66+
emoji_generator: !!python/name:material.extensions.emoji.to_svg
67+
68+
nav:
69+
- index.md
70+
- python-api.md

pyproject.toml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
dependencies = [
1818
"ipython",
1919
"ipytest",
20+
"nbformat>=5.10.4",
2021
"pytest",
2122
"typing-extensions; python_version < '3.11'",
2223
]
@@ -52,9 +53,7 @@
5253
"pytype; python_version <= '3.12'",
5354
"typing_extensions", # required for Python3.10 and below
5455
]
55-
test = [
56-
"pytest-doctest-mkdocstrings",
57-
]
56+
test = ["pytest-doctest-mkdocstrings"]
5857
cov = [
5958
{include-group = "test"},
6059
"pytest-cov",
@@ -64,14 +63,15 @@
6463
"mkdocs",
6564
"mkdocstrings[python]",
6665
"mkdocs-material",
66+
"pymdown-extensions",
6767
]
6868
dev = [
69-
"jupyter",
7069
{include-group = "lint"},
7170
{include-group = "typing"},
7271
{include-group = "test"},
7372
{include-group = "cov"},
7473
{include-group = "doc"},
74+
"jupyter", # python's toml decoder won't accept a text entry at the start of the array, only at the end!!
7575
]
7676

7777
[tool.pytest.ini_options]
@@ -99,7 +99,7 @@ exclude_also = [
9999
show_contexts = true
100100

101101
[tool.pytype]
102-
inputs = ["pytest_ipynb2"]
102+
inputs = ["pytest_ipynb2"]
103103

104104
[tool.ruff]
105105
line-length = 120
@@ -163,5 +163,9 @@ inputs = ["pytest_ipynb2"]
163163
]
164164

165165
"**/*.ipynb" = [
166-
"T201", # Allow `print` in Jupyter notebooks
166+
"T201", # Allow `print` in Jupyter notebooks
167+
"ANN", # Missing type annotations
168+
"S101", # Use of `assert`
169+
"PLR2004", # Magic number comparisons
170+
"D1", # Don't REQUIRE docstrings in notebooks - but lint them if they are there
167171
]

pytest_ipynb2/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
"""Pytest plugin to run tests in Jupyter Notebooks."""
1+
"""Pytest plugin to run tests in Jupyter Notebooks."""
2+
3+
from ._ipynb_parser import Notebook

pytest_ipynb2/_ipynb_parser.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""Parse notebooks."""
2+
3+
import json
4+
from pathlib import Path
5+
6+
import nbformat
7+
8+
9+
class Notebook:
10+
"""
11+
An ipython Notebook.
12+
13+
- constructor from Path
14+
- methods to get various cell types
15+
- a `test` cell type identified by the `%%ipytest` cell magic.
16+
"""
17+
18+
def __init__(self, filepath: Path) -> None:
19+
rawcontents = filepath.read_text()
20+
contents = json.loads(rawcontents)
21+
nbformat.validate(contents)
22+
self.contents = contents
23+
24+
def getcodecells(self) -> dict[int, str]:
25+
"""Return parsed code cells from a notebook."""
26+
return {
27+
cellnr: "".join(cell["source"])
28+
for cellnr, cell in enumerate(self.contents["cells"])
29+
if cell["cell_type"] == "code" and not cell["source"][0].startswith(r"%%ipytest")
30+
}
31+
32+
def gettestcells(self) -> dict[int, str]:
33+
"""Return parsed test cells from a notebook. Identified by cell magic `%%ipytest`."""
34+
return {
35+
cellnr: "".join(cell["source"][1:]).strip()
36+
for cellnr, cell in enumerate(self.contents["cells"])
37+
if cell["cell_type"] == "code" and cell["source"][0].startswith(r"%%ipytest")
38+
}

ruff-main.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Additional linting rules applied to PRs and `main`branch
2+
extend = "pyproject.toml"
3+
lint.extend-select = [
4+
"TD", # TODOs need a description, even outside of `main`
5+
]

tests/assets/notebook.ipynb

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# A notebook to test against"
8+
]
9+
},
10+
{
11+
"cell_type": "code",
12+
"execution_count": null,
13+
"metadata": {},
14+
"outputs": [],
15+
"source": [
16+
"# This cell sets some global variables\n",
17+
"\n",
18+
"x = 1\n",
19+
"y = 2\n",
20+
"\n",
21+
"x + y\n"
22+
]
23+
},
24+
{
25+
"cell_type": "markdown",
26+
"metadata": {},
27+
"source": [
28+
"Includes a few functions and tests ..."
29+
]
30+
},
31+
{
32+
"cell_type": "code",
33+
"execution_count": null,
34+
"metadata": {},
35+
"outputs": [],
36+
"source": [
37+
"# Define a function\n",
38+
"\n",
39+
"def adder(a, b):\n",
40+
" return a+b"
41+
]
42+
},
43+
{
44+
"cell_type": "code",
45+
"execution_count": null,
46+
"metadata": {},
47+
"outputs": [],
48+
"source": [
49+
"%%ipytest\n",
50+
"\n",
51+
"def test_adder():\n",
52+
" assert adder(1,2) == 3\n",
53+
"\n",
54+
"def test_globals():\n",
55+
" assert x == 1"
56+
]
57+
},
58+
{
59+
"cell_type": "code",
60+
"execution_count": null,
61+
"metadata": {},
62+
"outputs": [],
63+
"source": [
64+
"def another_function(*args):\n",
65+
" return args"
66+
]
67+
}
68+
],
69+
"metadata": {
70+
"kernelspec": {
71+
"display_name": ".venv",
72+
"language": "python",
73+
"name": "python3"
74+
},
75+
"language_info": {
76+
"codemirror_mode": {
77+
"name": "ipython",
78+
"version": 3
79+
},
80+
"file_extension": ".py",
81+
"mimetype": "text/x-python",
82+
"name": "python",
83+
"nbconvert_exporter": "python",
84+
"pygments_lexer": "ipython3",
85+
"version": "3.13.1"
86+
}
87+
},
88+
"nbformat": 4,
89+
"nbformat_minor": 2
90+
}

tests/assets/notebook.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#ruff: noqa
2+
# This cell sets some global variables
3+
4+
x = 1
5+
y = 2
6+
7+
x + y
8+
9+
# Define a function
10+
11+
def adder(a, b):
12+
return a+b
13+
14+
def test_adder():
15+
assert adder(1,2) == 3
16+
17+
def test_globals():
18+
assert x == 1
19+
20+
def another_function(*args):
21+
return args

0 commit comments

Comments
 (0)