pypad-notebook 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pypad_notebook-0.1.0/LICENSE +21 -0
- pypad_notebook-0.1.0/MANIFEST.in +2 -0
- pypad_notebook-0.1.0/PKG-INFO +111 -0
- pypad_notebook-0.1.0/README.md +89 -0
- pypad_notebook-0.1.0/pyproject.toml +42 -0
- pypad_notebook-0.1.0/setup.cfg +4 -0
- pypad_notebook-0.1.0/src/pypad/__init__.py +59 -0
- pypad_notebook-0.1.0/src/pypad/__main__.py +5 -0
- pypad_notebook-0.1.0/src/pypad/_schema.json +373 -0
- pypad_notebook-0.1.0/src/pypad/cli.py +192 -0
- pypad_notebook-0.1.0/src/pypad/embed.py +70 -0
- pypad_notebook-0.1.0/src/pypad/format.py +354 -0
- pypad_notebook-0.1.0/src/pypad/ipynb.py +193 -0
- pypad_notebook-0.1.0/src/pypad/jsutil.py +72 -0
- pypad_notebook-0.1.0/src/pypad/merge.py +174 -0
- pypad_notebook-0.1.0/src/pypad/model.py +148 -0
- pypad_notebook-0.1.0/src/pypad/outputs.py +60 -0
- pypad_notebook-0.1.0/src/pypad/py.typed +0 -0
- pypad_notebook-0.1.0/src/pypad/rules.py +182 -0
- pypad_notebook-0.1.0/src/pypad/structure.py +69 -0
- pypad_notebook-0.1.0/src/pypad/verify.py +60 -0
- pypad_notebook-0.1.0/src/pypad_notebook.egg-info/PKG-INFO +111 -0
- pypad_notebook-0.1.0/src/pypad_notebook.egg-info/SOURCES.txt +30 -0
- pypad_notebook-0.1.0/src/pypad_notebook.egg-info/dependency_links.txt +1 -0
- pypad_notebook-0.1.0/src/pypad_notebook.egg-info/entry_points.txt +2 -0
- pypad_notebook-0.1.0/src/pypad_notebook.egg-info/requires.txt +3 -0
- pypad_notebook-0.1.0/src/pypad_notebook.egg-info/top_level.txt +1 -0
- pypad_notebook-0.1.0/tests/test_behaviour.py +62 -0
- pypad_notebook-0.1.0/tests/test_cli.py +93 -0
- pypad_notebook-0.1.0/tests/test_conformance.py +72 -0
- pypad_notebook-0.1.0/tests/test_rules.py +52 -0
- pypad_notebook-0.1.0/tests/vectors.json +28371 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pughlu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pypad-notebook
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Read, write and convert PyPad notebooks (.pypad.py) and Jupyter notebooks (.ipynb).
|
|
5
|
+
Author: Philip Lewis
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://pypad.dev
|
|
8
|
+
Project-URL: Repository, https://github.com/pypad-dev/pypad
|
|
9
|
+
Keywords: notebook,jupyter,ipynb,pypad,moodle,education
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Education
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Education
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Provides-Extra: test
|
|
20
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# pypad-notebook
|
|
24
|
+
|
|
25
|
+
Read, write and convert [PyPad](https://pypad.dev) notebooks (`.pypad.py`) and Jupyter notebooks (`.ipynb`). Standard library
|
|
26
|
+
only; Python 3.9+.
|
|
27
|
+
|
|
28
|
+
PyPad is a small in-browser Python notebook for teaching: it runs entirely in the learner's browser, fits in a Moodle quiz question, can be
|
|
29
|
+
shared as a link, and its files are plain, readable Python. This package is the file-format side of it, so a notebook is never trapped.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install pypad-notebook
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Command line
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pypad convert lesson.pypad.py # -> lesson.ipynb
|
|
39
|
+
pypad convert lesson.ipynb # -> lesson.pypad.py
|
|
40
|
+
pypad convert lesson.ipynb --outputs text
|
|
41
|
+
pypad check lesson.pypad.py # ok / changed / absent: is it still what PyPad wrote? (exit 1 if changed)
|
|
42
|
+
pypad embed lesson.pypad.py > question.html # the HTML block for a Moodle question or web page
|
|
43
|
+
pypad answers responses.csv --out answers/ # split a Moodle quiz "Responses" download into one file per student
|
|
44
|
+
pypad verify template.pypad.py answers/*.pypad.py # re-check them against the template (exit 1 if any are not clean)
|
|
45
|
+
pypad embed lesson.pypad.py --answer-format base64 --loader https://pypad.dev/v1/pypad-mdl-quiz.js
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`convert` never overwrites without `--force`. `check`, `convert` and `embed` also accept an HTML page that holds a notebook (an embed, or a
|
|
49
|
+
PyPad "web page" export).
|
|
50
|
+
|
|
51
|
+
## Marking
|
|
52
|
+
|
|
53
|
+
An assessment that matters should be checked where the student cannot touch it. The widget stops mistakes in the student's browser, but the browser is
|
|
54
|
+
the student's. `pypad verify` applies, on your side, the same rules the widget applies when it opens an answer (the template's `policy`, its locked cells,
|
|
55
|
+
its answer cells and sections), and tells you for each answer what the student changed, added or removed, and what the rules would have put back:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
answers/Student_Sam.pypad.py: clean (check value ok) - changed a1
|
|
59
|
+
answers/Other_Oz.pypad.py: NOT CLEAN (check value ok) - changed a1
|
|
60
|
+
cell "i1" cannot be edited, and its text was put back
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`pypad answers` turns a Moodle quiz **Responses** download (CSV) into those files, decoding stored answers.
|
|
64
|
+
|
|
65
|
+
## In Python
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import pypad
|
|
69
|
+
|
|
70
|
+
result = pypad.parse(open("lesson.pypad.py", encoding="utf-8").read())
|
|
71
|
+
result.integrity # 'ok', 'changed' or 'absent'
|
|
72
|
+
result.warnings # problems found while reading; a damaged file still opens
|
|
73
|
+
nb = result.notebook # nb.config, nb.cells[i].id / .type / .source / .settings / .outputs
|
|
74
|
+
|
|
75
|
+
text = pypad.dumps(nb) # .pypad.py text, with its check value
|
|
76
|
+
ipynb = pypad.to_ipynb_text(nb, save_outputs="text")
|
|
77
|
+
nb2 = pypad.from_ipynb_text(open("lesson.ipynb", encoding="utf-8").read())
|
|
78
|
+
html = pypad.build_embed(text, answer_format="plain")
|
|
79
|
+
|
|
80
|
+
report = pypad.verify_answer(template_nb, answer_nb, integrity) # report.clean, .changed_cells, .violations, ...
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## It matches the JavaScript implementation
|
|
84
|
+
|
|
85
|
+
This is a port of the reference implementation (`packages/notebook` in the PyPad repository). The same text produces the same notebook, the
|
|
86
|
+
same file, the same Jupyter notebook and the same check value, including for emoji and odd characters. That is tested against a file of vectors
|
|
87
|
+
that the JavaScript implementation generates (`tests/vectors.json`), so the two cannot drift apart unnoticed.
|
|
88
|
+
|
|
89
|
+
## The format, in one screen
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
# %% [pypad-start] v1 sha=3f9a1c0d7b2e4a65
|
|
93
|
+
|
|
94
|
+
# %% [config] {"policy":"fixed","kernel":"skulpt"}
|
|
95
|
+
|
|
96
|
+
# %% [markdown] {"id":"k3j9a0xq","editable":false}
|
|
97
|
+
"""
|
|
98
|
+
Print the sum of 1 and 2.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
# %% [code] {"id":"p8w2m1zc"}
|
|
102
|
+
print(1 + 2)
|
|
103
|
+
|
|
104
|
+
# %% [pypad-end]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Code is code and everything else sits in triple-quoted strings, so the file is (nearly) valid Python and readable anywhere. The `sha` is a check
|
|
108
|
+
value of the notebook's content: it notices an editor or a hand edit changing something, and ignores indentation and line endings. It is not a
|
|
109
|
+
security measure. Reading is forgiving (Jupytext-style comment blocks, indented text, Windows line endings); writing has one form.
|
|
110
|
+
|
|
111
|
+
MIT licensed.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# pypad-notebook
|
|
2
|
+
|
|
3
|
+
Read, write and convert [PyPad](https://pypad.dev) notebooks (`.pypad.py`) and Jupyter notebooks (`.ipynb`). Standard library
|
|
4
|
+
only; Python 3.9+.
|
|
5
|
+
|
|
6
|
+
PyPad is a small in-browser Python notebook for teaching: it runs entirely in the learner's browser, fits in a Moodle quiz question, can be
|
|
7
|
+
shared as a link, and its files are plain, readable Python. This package is the file-format side of it, so a notebook is never trapped.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install pypad-notebook
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Command line
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pypad convert lesson.pypad.py # -> lesson.ipynb
|
|
17
|
+
pypad convert lesson.ipynb # -> lesson.pypad.py
|
|
18
|
+
pypad convert lesson.ipynb --outputs text
|
|
19
|
+
pypad check lesson.pypad.py # ok / changed / absent: is it still what PyPad wrote? (exit 1 if changed)
|
|
20
|
+
pypad embed lesson.pypad.py > question.html # the HTML block for a Moodle question or web page
|
|
21
|
+
pypad answers responses.csv --out answers/ # split a Moodle quiz "Responses" download into one file per student
|
|
22
|
+
pypad verify template.pypad.py answers/*.pypad.py # re-check them against the template (exit 1 if any are not clean)
|
|
23
|
+
pypad embed lesson.pypad.py --answer-format base64 --loader https://pypad.dev/v1/pypad-mdl-quiz.js
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`convert` never overwrites without `--force`. `check`, `convert` and `embed` also accept an HTML page that holds a notebook (an embed, or a
|
|
27
|
+
PyPad "web page" export).
|
|
28
|
+
|
|
29
|
+
## Marking
|
|
30
|
+
|
|
31
|
+
An assessment that matters should be checked where the student cannot touch it. The widget stops mistakes in the student's browser, but the browser is
|
|
32
|
+
the student's. `pypad verify` applies, on your side, the same rules the widget applies when it opens an answer (the template's `policy`, its locked cells,
|
|
33
|
+
its answer cells and sections), and tells you for each answer what the student changed, added or removed, and what the rules would have put back:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
answers/Student_Sam.pypad.py: clean (check value ok) - changed a1
|
|
37
|
+
answers/Other_Oz.pypad.py: NOT CLEAN (check value ok) - changed a1
|
|
38
|
+
cell "i1" cannot be edited, and its text was put back
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`pypad answers` turns a Moodle quiz **Responses** download (CSV) into those files, decoding stored answers.
|
|
42
|
+
|
|
43
|
+
## In Python
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import pypad
|
|
47
|
+
|
|
48
|
+
result = pypad.parse(open("lesson.pypad.py", encoding="utf-8").read())
|
|
49
|
+
result.integrity # 'ok', 'changed' or 'absent'
|
|
50
|
+
result.warnings # problems found while reading; a damaged file still opens
|
|
51
|
+
nb = result.notebook # nb.config, nb.cells[i].id / .type / .source / .settings / .outputs
|
|
52
|
+
|
|
53
|
+
text = pypad.dumps(nb) # .pypad.py text, with its check value
|
|
54
|
+
ipynb = pypad.to_ipynb_text(nb, save_outputs="text")
|
|
55
|
+
nb2 = pypad.from_ipynb_text(open("lesson.ipynb", encoding="utf-8").read())
|
|
56
|
+
html = pypad.build_embed(text, answer_format="plain")
|
|
57
|
+
|
|
58
|
+
report = pypad.verify_answer(template_nb, answer_nb, integrity) # report.clean, .changed_cells, .violations, ...
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## It matches the JavaScript implementation
|
|
62
|
+
|
|
63
|
+
This is a port of the reference implementation (`packages/notebook` in the PyPad repository). The same text produces the same notebook, the
|
|
64
|
+
same file, the same Jupyter notebook and the same check value, including for emoji and odd characters. That is tested against a file of vectors
|
|
65
|
+
that the JavaScript implementation generates (`tests/vectors.json`), so the two cannot drift apart unnoticed.
|
|
66
|
+
|
|
67
|
+
## The format, in one screen
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
# %% [pypad-start] v1 sha=3f9a1c0d7b2e4a65
|
|
71
|
+
|
|
72
|
+
# %% [config] {"policy":"fixed","kernel":"skulpt"}
|
|
73
|
+
|
|
74
|
+
# %% [markdown] {"id":"k3j9a0xq","editable":false}
|
|
75
|
+
"""
|
|
76
|
+
Print the sum of 1 and 2.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
# %% [code] {"id":"p8w2m1zc"}
|
|
80
|
+
print(1 + 2)
|
|
81
|
+
|
|
82
|
+
# %% [pypad-end]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Code is code and everything else sits in triple-quoted strings, so the file is (nearly) valid Python and readable anywhere. The `sha` is a check
|
|
86
|
+
value of the notebook's content: it notices an editor or a hand edit changing something, and ignores indentation and line endings. It is not a
|
|
87
|
+
security measure. Reading is forgiving (Jupytext-style comment blocks, indented text, Windows line endings); writing has one form.
|
|
88
|
+
|
|
89
|
+
MIT licensed.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pypad-notebook"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Read, write and convert PyPad notebooks (.pypad.py) and Jupyter notebooks (.ipynb)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Philip Lewis" }]
|
|
13
|
+
keywords = ["notebook", "jupyter", "ipynb", "pypad", "moodle", "education"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Education",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Topic :: Education",
|
|
20
|
+
"Typing :: Typed",
|
|
21
|
+
]
|
|
22
|
+
dependencies = []
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
test = ["pytest>=7"]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
pypad = "pypad.cli:main"
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://pypad.dev"
|
|
32
|
+
Repository = "https://github.com/pypad-dev/pypad"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.package-data]
|
|
38
|
+
pypad = ["py.typed", "_schema.json"]
|
|
39
|
+
|
|
40
|
+
[tool.pytest.ini_options]
|
|
41
|
+
testpaths = ["tests"]
|
|
42
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""pypad: read, write and convert PyPad notebooks (``.pypad.py`` and ``.pypad.py`` files) and Jupyter notebooks (``.ipynb``).
|
|
2
|
+
|
|
3
|
+
>>> import pypad
|
|
4
|
+
>>> result = pypad.parse(open("lesson.pypad.py").read())
|
|
5
|
+
>>> result.integrity # 'ok', 'changed' or 'absent': is it still what PyPad wrote?
|
|
6
|
+
'ok'
|
|
7
|
+
>>> nb = pypad.from_ipynb_text(open("lesson.ipynb").read())
|
|
8
|
+
>>> open("lesson.pypad.py", "w").write(pypad.dumps(nb))
|
|
9
|
+
|
|
10
|
+
A faithful port of the PyPad reference implementation (https://github.com/pypad-dev/pypad, ``packages/notebook``): the same text and
|
|
11
|
+
the same check value come out of Python as out of JavaScript. Standard library only.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
from typing import Any, Callable, Optional
|
|
18
|
+
|
|
19
|
+
from .embed import build_embed, from_script_block, to_script_block
|
|
20
|
+
from .format import FORMAT_VERSION, ParseResult, dedent, dumps, parse
|
|
21
|
+
from .ipynb import from_ipynb, to_ipynb
|
|
22
|
+
from .jsutil import js_json
|
|
23
|
+
from .merge import MergeResult, merge_answer
|
|
24
|
+
from .rules import EffectiveCell, resolve_cell, resolve_config
|
|
25
|
+
from .verify import AnswerReport, verify_answer
|
|
26
|
+
from .model import (
|
|
27
|
+
Cell,
|
|
28
|
+
Notebook,
|
|
29
|
+
SavedOutputs,
|
|
30
|
+
hash_document,
|
|
31
|
+
hash_source,
|
|
32
|
+
is_output_stale,
|
|
33
|
+
new_cell_id,
|
|
34
|
+
positional_ids,
|
|
35
|
+
)
|
|
36
|
+
from .outputs import select_outputs
|
|
37
|
+
|
|
38
|
+
__version__ = "0.1.0"
|
|
39
|
+
|
|
40
|
+
#: File extensions that hold a PyPad notebook. Reading does not depend on the name: any text with the markers opens.
|
|
41
|
+
NOTEBOOK_EXTENSIONS = (".pypad.py",)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def to_ipynb_text(notebook: Notebook, save_outputs: str = "none", max_saved_output_chars: float = 5000) -> str:
|
|
45
|
+
"""A Jupyter notebook as text, written the way Jupyter writes it (one space of indent, sorted keys, final newline)."""
|
|
46
|
+
return json.dumps(to_ipynb(notebook, save_outputs, max_saved_output_chars), indent=1, sort_keys=True, ensure_ascii=False) + "\n"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def from_ipynb_text(text: str, make_id: Optional[Callable[[], str]] = None) -> Notebook:
|
|
50
|
+
"""A notebook from the text of an ``.ipynb`` file. Problems are dropped quietly; use :func:`from_ipynb` to see the warnings."""
|
|
51
|
+
return from_ipynb(json.loads(text), make_id)[0]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
"AnswerReport", "EffectiveCell", "MergeResult", "merge_answer", "resolve_cell", "resolve_config", "verify_answer",
|
|
56
|
+
"Cell", "FORMAT_VERSION", "NOTEBOOK_EXTENSIONS", "Notebook", "ParseResult", "SavedOutputs",
|
|
57
|
+
"build_embed", "dedent", "dumps", "from_ipynb", "from_ipynb_text", "from_script_block", "hash_document", "hash_source",
|
|
58
|
+
"is_output_stale", "js_json", "new_cell_id", "parse", "positional_ids", "select_outputs", "to_ipynb", "to_ipynb_text", "to_script_block",
|
|
59
|
+
]
|