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.
Files changed (32) hide show
  1. pypad_notebook-0.1.0/LICENSE +21 -0
  2. pypad_notebook-0.1.0/MANIFEST.in +2 -0
  3. pypad_notebook-0.1.0/PKG-INFO +111 -0
  4. pypad_notebook-0.1.0/README.md +89 -0
  5. pypad_notebook-0.1.0/pyproject.toml +42 -0
  6. pypad_notebook-0.1.0/setup.cfg +4 -0
  7. pypad_notebook-0.1.0/src/pypad/__init__.py +59 -0
  8. pypad_notebook-0.1.0/src/pypad/__main__.py +5 -0
  9. pypad_notebook-0.1.0/src/pypad/_schema.json +373 -0
  10. pypad_notebook-0.1.0/src/pypad/cli.py +192 -0
  11. pypad_notebook-0.1.0/src/pypad/embed.py +70 -0
  12. pypad_notebook-0.1.0/src/pypad/format.py +354 -0
  13. pypad_notebook-0.1.0/src/pypad/ipynb.py +193 -0
  14. pypad_notebook-0.1.0/src/pypad/jsutil.py +72 -0
  15. pypad_notebook-0.1.0/src/pypad/merge.py +174 -0
  16. pypad_notebook-0.1.0/src/pypad/model.py +148 -0
  17. pypad_notebook-0.1.0/src/pypad/outputs.py +60 -0
  18. pypad_notebook-0.1.0/src/pypad/py.typed +0 -0
  19. pypad_notebook-0.1.0/src/pypad/rules.py +182 -0
  20. pypad_notebook-0.1.0/src/pypad/structure.py +69 -0
  21. pypad_notebook-0.1.0/src/pypad/verify.py +60 -0
  22. pypad_notebook-0.1.0/src/pypad_notebook.egg-info/PKG-INFO +111 -0
  23. pypad_notebook-0.1.0/src/pypad_notebook.egg-info/SOURCES.txt +30 -0
  24. pypad_notebook-0.1.0/src/pypad_notebook.egg-info/dependency_links.txt +1 -0
  25. pypad_notebook-0.1.0/src/pypad_notebook.egg-info/entry_points.txt +2 -0
  26. pypad_notebook-0.1.0/src/pypad_notebook.egg-info/requires.txt +3 -0
  27. pypad_notebook-0.1.0/src/pypad_notebook.egg-info/top_level.txt +1 -0
  28. pypad_notebook-0.1.0/tests/test_behaviour.py +62 -0
  29. pypad_notebook-0.1.0/tests/test_cli.py +93 -0
  30. pypad_notebook-0.1.0/tests/test_conformance.py +72 -0
  31. pypad_notebook-0.1.0/tests/test_rules.py +52 -0
  32. 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,2 @@
1
+ include LICENSE README.md
2
+ include tests/vectors.json
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ ]
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())