pyh5p 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.
- pyh5p-0.1.0/.github/workflows/ci.yml +23 -0
- pyh5p-0.1.0/.gitignore +33 -0
- pyh5p-0.1.0/CHANGELOG.md +11 -0
- pyh5p-0.1.0/LICENSE +22 -0
- pyh5p-0.1.0/PKG-INFO +245 -0
- pyh5p-0.1.0/README.md +186 -0
- pyh5p-0.1.0/answers.json +13 -0
- pyh5p-0.1.0/examples/round_trip.py +7 -0
- pyh5p-0.1.0/fixtures/README.md +17 -0
- pyh5p-0.1.0/fixtures/examples/synthetic.json +10 -0
- pyh5p-0.1.0/fixtures/libraries/H5P.NestedFixture-1.0/library.js +1 -0
- pyh5p-0.1.0/fixtures/libraries/H5P.NestedFixture-1.0/library.json +9 -0
- pyh5p-0.1.0/fixtures/libraries/H5P.NestedFixture-1.0/semantics.json +3 -0
- pyh5p-0.1.0/fixtures/libraries/H5P.SyntheticAllTypes-1.0/css/synthetic.css +1 -0
- pyh5p-0.1.0/fixtures/libraries/H5P.SyntheticAllTypes-1.0/js/synthetic.js +2 -0
- pyh5p-0.1.0/fixtures/libraries/H5P.SyntheticAllTypes-1.0/library.json +17 -0
- pyh5p-0.1.0/fixtures/libraries/H5P.SyntheticAllTypes-1.0/presave.js +2 -0
- pyh5p-0.1.0/fixtures/libraries/H5P.SyntheticAllTypes-1.0/semantics.json +15 -0
- pyh5p-0.1.0/fixtures/libraries/H5P.SyntheticAllTypes-1.0/upgrades.js +2 -0
- pyh5p-0.1.0/fixtures/libraries/H5P.SyntheticDependency-1.0/library.js +1 -0
- pyh5p-0.1.0/fixtures/libraries/H5P.SyntheticDependency-1.0/library.json +9 -0
- pyh5p-0.1.0/fixtures/libraries/H5P.SyntheticDynamic-1.0/library.js +1 -0
- pyh5p-0.1.0/fixtures/libraries/H5P.SyntheticDynamic-1.0/library.json +8 -0
- pyh5p-0.1.0/fixtures/libraries/H5PEditor.SyntheticWidget-1.0/library.js +1 -0
- pyh5p-0.1.0/fixtures/libraries/H5PEditor.SyntheticWidget-1.0/library.json +8 -0
- pyh5p-0.1.0/pyproject.toml +69 -0
- pyh5p-0.1.0/scripts/differential_check.py +51 -0
- pyh5p-0.1.0/scripts/discover_types.py +53 -0
- pyh5p-0.1.0/src/pyh5p/__init__.py +44 -0
- pyh5p-0.1.0/src/pyh5p/_util.py +90 -0
- pyh5p-0.1.0/src/pyh5p/cli.py +169 -0
- pyh5p-0.1.0/src/pyh5p/content.py +567 -0
- pyh5p-0.1.0/src/pyh5p/errors.py +53 -0
- pyh5p-0.1.0/src/pyh5p/hooks.py +55 -0
- pyh5p-0.1.0/src/pyh5p/library.py +850 -0
- pyh5p-0.1.0/src/pyh5p/package.py +267 -0
- pyh5p-0.1.0/src/pyh5p/py.typed +1 -0
- pyh5p-0.1.0/src/pyh5p/security.py +183 -0
- pyh5p-0.1.0/src/pyh5p/semantics.py +643 -0
- pyh5p-0.1.0/tests/conftest.py +15 -0
- pyh5p-0.1.0/tests/test_hooks.py +9 -0
- pyh5p-0.1.0/tests/test_library.py +173 -0
- pyh5p-0.1.0/tests/test_packages.py +126 -0
- pyh5p-0.1.0/tests/test_properties.py +22 -0
- pyh5p-0.1.0/tests/test_semantics.py +80 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
strategy:
|
|
10
|
+
fail-fast: false
|
|
11
|
+
matrix:
|
|
12
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
13
|
+
python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
14
|
+
runs-on: ${{ matrix.os }}
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python }}
|
|
20
|
+
- run: python -m pip install -e ".[dev]"
|
|
21
|
+
- run: python -m pytest --cov=pyh5p --cov-report=term-missing
|
|
22
|
+
- run: ruff check .
|
|
23
|
+
- run: mypy src/pyh5p
|
pyh5p-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
.DS_Store
|
|
4
|
+
Thumbs.db
|
|
5
|
+
|
|
6
|
+
.pytest_cache/
|
|
7
|
+
.pytest-tmp/
|
|
8
|
+
.pytest-*/
|
|
9
|
+
.hypothesis/
|
|
10
|
+
.mypy_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
.coverage
|
|
13
|
+
coverage.xml
|
|
14
|
+
htmlcov/
|
|
15
|
+
|
|
16
|
+
dist/
|
|
17
|
+
build/
|
|
18
|
+
*.egg-info/
|
|
19
|
+
*.whl
|
|
20
|
+
|
|
21
|
+
.venv/
|
|
22
|
+
.release-test/
|
|
23
|
+
.env
|
|
24
|
+
.tox/
|
|
25
|
+
.nox/
|
|
26
|
+
.pyright/
|
|
27
|
+
|
|
28
|
+
# Local H5P outputs and downloaded library caches.
|
|
29
|
+
*.h5p
|
|
30
|
+
*.zip
|
|
31
|
+
.pyh5p-libraries/
|
|
32
|
+
unpacked/
|
|
33
|
+
fixtures/downloaded/
|
pyh5p-0.1.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 - 2026-09-12
|
|
4
|
+
|
|
5
|
+
Initial public release of the generic semantics engine, local library store,
|
|
6
|
+
Hub and registry clients, secure package reader/writer, validator, JSON Schema
|
|
7
|
+
exporter, CLI, fixture tooling, and optional JavaScript hook boundary.
|
|
8
|
+
|
|
9
|
+
Automatic builds now use complete distributable Hub packages, including built
|
|
10
|
+
runtime files. The CLI can target the historical Core API 1.27 package set
|
|
11
|
+
with `--core-api 1.27`.
|
pyh5p-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nikita Berger
|
|
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.
|
|
22
|
+
|
pyh5p-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pyh5p
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Schema-driven creation, inspection, validation and packaging of H5P content
|
|
5
|
+
Project-URL: Homepage, https://github.com/Nikityyy/pyh5p
|
|
6
|
+
Project-URL: Repository, https://github.com/Nikityyy/pyh5p
|
|
7
|
+
Project-URL: Issues, https://github.com/Nikityyy/pyh5p/issues
|
|
8
|
+
Project-URL: Author, https://nikity.is-a.dev
|
|
9
|
+
Author-email: Nikita Berger <bergernikita1807@gmail.com>
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026 Nikita Berger
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
|
|
32
|
+
License-File: LICENSE
|
|
33
|
+
Keywords: content,elearning,h5p,html5,lms
|
|
34
|
+
Classifier: Development Status :: 3 - Alpha
|
|
35
|
+
Classifier: Intended Audience :: Developers
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
43
|
+
Classifier: Topic :: Education
|
|
44
|
+
Requires-Python: >=3.10
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
47
|
+
Requires-Dist: hatchling>=1.25; extra == 'dev'
|
|
48
|
+
Requires-Dist: hypothesis>=6.100; extra == 'dev'
|
|
49
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
50
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
51
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
52
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
53
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
54
|
+
Provides-Extra: html
|
|
55
|
+
Requires-Dist: nh3>=0.2; extra == 'html'
|
|
56
|
+
Provides-Extra: js
|
|
57
|
+
Requires-Dist: quickjs>=1.19; extra == 'js'
|
|
58
|
+
Description-Content-Type: text/markdown
|
|
59
|
+
|
|
60
|
+
# pyh5p
|
|
61
|
+
|
|
62
|
+
`pyh5p` creates, edits, validates, and packages H5P content in Python. It
|
|
63
|
+
reads each library's `library.json` and `semantics.json`, so the Python code
|
|
64
|
+
does not need a class for every H5P content type.
|
|
65
|
+
|
|
66
|
+
## Quick start
|
|
67
|
+
|
|
68
|
+
Install the package:
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
pip install pyh5p
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Put the content data in `answers.json`:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"question": "What is 2 + 2?",
|
|
79
|
+
"answers": [
|
|
80
|
+
{"text": "4", "correct": true},
|
|
81
|
+
{"text": "5", "correct": false}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Build the package:
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
pyh5p build H5P.MultiChoice answers.json question.h5p
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The command fetches a complete package and its dependencies from the official
|
|
93
|
+
H5P Content Type Hub when they are not already cached. Fetching is enabled by
|
|
94
|
+
default. `question.h5p` is ready to import into an H5P host.
|
|
95
|
+
|
|
96
|
+
For Lumi Desktop or Moodle installations that run Core API 1.27, request the
|
|
97
|
+
matching historical package set:
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
pyh5p build H5P.MultiChoice answers.json question.h5p --core-api 1.27
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Python API
|
|
104
|
+
|
|
105
|
+
The same operation is available without a template or content-type-specific
|
|
106
|
+
Python code:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
import pyh5p
|
|
110
|
+
|
|
111
|
+
content = pyh5p.build(
|
|
112
|
+
"H5P.MultiChoice",
|
|
113
|
+
{
|
|
114
|
+
"question": "What is 2 + 2?",
|
|
115
|
+
"answers": [
|
|
116
|
+
{"text": "4", "correct": True},
|
|
117
|
+
{"text": "5", "correct": False},
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
)
|
|
121
|
+
content.save("question.h5p")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The field names and validation rules come from the installed library's
|
|
125
|
+
semantics. The same API works with other runnable libraries, including local,
|
|
126
|
+
historical, and third-party libraries.
|
|
127
|
+
|
|
128
|
+
Media uses the same generic path. For example:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
content = pyh5p.build("H5P.Image", {"decorative": True}, validate=False)
|
|
132
|
+
content.import_media(
|
|
133
|
+
"file", b"image bytes", target="images/example.png", mime="image/png"
|
|
134
|
+
)
|
|
135
|
+
content.save("image.h5p")
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`LibraryStore` accepts local library directories and archives. Use it for
|
|
139
|
+
offline builds or for a controlled library set:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
store = pyh5p.LibraryStore("libraries")
|
|
143
|
+
content = pyh5p.build("H5P.MultiChoice", data, store=store, fetch=False)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## What is generic
|
|
147
|
+
|
|
148
|
+
The semantics engine handles groups, lists, nested libraries, text and HTML,
|
|
149
|
+
numbers, booleans, selects, image/video/audio/file fields, defaults, optional
|
|
150
|
+
values, regular expressions, ranges, allowed tags, widgets, `showWhen`,
|
|
151
|
+
`isSubContent`, unknown attributes, and recursive dependencies.
|
|
152
|
+
|
|
153
|
+
It also supports dependency closure for preloaded, dynamic, editor, and
|
|
154
|
+
nested-content libraries. Packages can be loaded, edited, validated, and
|
|
155
|
+
exported again. Builds use stable JSON ordering and ZIP timestamps where the
|
|
156
|
+
H5P format allows it.
|
|
157
|
+
|
|
158
|
+
## Command line
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
pyh5p build H5P.MultiChoice answers.json question.h5p
|
|
162
|
+
pyh5p inspect question.h5p
|
|
163
|
+
pyh5p validate question.h5p
|
|
164
|
+
pyh5p schema H5P.MultiChoice --libraries .pyh5p-libraries -o schema.json
|
|
165
|
+
pyh5p unpack question.h5p unpacked
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Use `--offline` with `build` when network access is not allowed. Use
|
|
169
|
+
`--cache-dir` to choose the library cache. `--content-only` is available when
|
|
170
|
+
the destination explicitly accepts a package without libraries.
|
|
171
|
+
|
|
172
|
+
To preview a package, import it into [Lumi Desktop](https://lumi.education/en/lumi-h5p-offline-desktop-editor/)
|
|
173
|
+
and open its preview. The H5P host runs the library JavaScript; pyh5p creates
|
|
174
|
+
the content and portable package.
|
|
175
|
+
|
|
176
|
+
## JSON Schema and AI applications
|
|
177
|
+
|
|
178
|
+
Ask pyh5p for the schema, pass it to any provider's structured-output API,
|
|
179
|
+
then validate and build the returned object:
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
schema = pyh5p.schema_json("H5P.MultiChoice", store=store)
|
|
183
|
+
returned = provider.generate_json(schema=schema)
|
|
184
|
+
content = pyh5p.build("H5P.MultiChoice", returned, store=store)
|
|
185
|
+
content.require_valid()
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The core package has no dependency on an AI provider.
|
|
189
|
+
|
|
190
|
+
## Compatibility and JavaScript hooks
|
|
191
|
+
|
|
192
|
+
Compatibility means that pyh5p parses the supplied definitions, validates the
|
|
193
|
+
semantic data, resolves dependencies, preserves distributable library files,
|
|
194
|
+
and writes the standard H5P package structure. The host must support the Core
|
|
195
|
+
API version declared by the selected libraries. Current Hub packages may
|
|
196
|
+
require Core API 1.28; `--core-api 1.27` selects the historical official Hub
|
|
197
|
+
package set for older hosts.
|
|
198
|
+
|
|
199
|
+
`presave.js` and `upgrades.js` are detected but never executed implicitly.
|
|
200
|
+
They can contain migrations that semantics alone cannot express. Applications
|
|
201
|
+
that need those migrations must provide a sandboxed JavaScript adapter through
|
|
202
|
+
the optional `js` extra, then validate the migrated data.
|
|
203
|
+
|
|
204
|
+
Historical packages can be installed from local sources when the current Hub
|
|
205
|
+
no longer serves their versions. pyh5p does not rewrite a library's declared
|
|
206
|
+
Core API or claim that a newer runtime works on an older host.
|
|
207
|
+
|
|
208
|
+
## Security
|
|
209
|
+
|
|
210
|
+
Archive reading and extraction reject traversal, symlinks, duplicate entries,
|
|
211
|
+
large archives, unsafe compression ratios, and disallowed extensions. Remote
|
|
212
|
+
downloads require HTTPS, an allowlisted host, and public DNS resolution.
|
|
213
|
+
Downloads happen only when fetching is enabled. Library JavaScript is never
|
|
214
|
+
executed by the Python package. Applications that display HTML or media must
|
|
215
|
+
apply their own sanitization and content policy.
|
|
216
|
+
|
|
217
|
+
## Development
|
|
218
|
+
|
|
219
|
+
```text
|
|
220
|
+
python -m pip install -e ".[dev]"
|
|
221
|
+
python -m pytest --basetemp .pytest-local
|
|
222
|
+
ruff check .
|
|
223
|
+
mypy src/pyh5p
|
|
224
|
+
python -m build
|
|
225
|
+
python -m twine check dist\*
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The checked-in tests cover semantics, validation paths, dependency graphs,
|
|
229
|
+
nested content, media, round trips, archive security, Hub package selection,
|
|
230
|
+
and malformed input. `scripts/discover_types.py` can fetch an opt-in local
|
|
231
|
+
registry corpus. `scripts/differential_check.py` can run an external validator
|
|
232
|
+
when one is configured. PHP, Lumi, WordPress, and Moodle are not dependencies
|
|
233
|
+
of the package or CI job.
|
|
234
|
+
|
|
235
|
+
Contributions should include a regression test for each fixed bug. Keep the
|
|
236
|
+
core clean-room and do not copy code from the GPL H5P PHP libraries.
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
The project is licensed under the [MIT License](LICENSE) and maintained by
|
|
241
|
+
[Nikita Berger](https://nikity.is-a.dev).
|
|
242
|
+
|
|
243
|
+
H5P is a trademark of H5P Group. pyh5p is independent and is not affiliated
|
|
244
|
+
with or endorsed by H5P Group, H5P.org, Lumi, Moodle, or WordPress. H5P
|
|
245
|
+
libraries retain their own licenses.
|
pyh5p-0.1.0/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# pyh5p
|
|
2
|
+
|
|
3
|
+
`pyh5p` creates, edits, validates, and packages H5P content in Python. It
|
|
4
|
+
reads each library's `library.json` and `semantics.json`, so the Python code
|
|
5
|
+
does not need a class for every H5P content type.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
Install the package:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
pip install pyh5p
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Put the content data in `answers.json`:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"question": "What is 2 + 2?",
|
|
20
|
+
"answers": [
|
|
21
|
+
{"text": "4", "correct": true},
|
|
22
|
+
{"text": "5", "correct": false}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Build the package:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
pyh5p build H5P.MultiChoice answers.json question.h5p
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The command fetches a complete package and its dependencies from the official
|
|
34
|
+
H5P Content Type Hub when they are not already cached. Fetching is enabled by
|
|
35
|
+
default. `question.h5p` is ready to import into an H5P host.
|
|
36
|
+
|
|
37
|
+
For Lumi Desktop or Moodle installations that run Core API 1.27, request the
|
|
38
|
+
matching historical package set:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
pyh5p build H5P.MultiChoice answers.json question.h5p --core-api 1.27
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Python API
|
|
45
|
+
|
|
46
|
+
The same operation is available without a template or content-type-specific
|
|
47
|
+
Python code:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import pyh5p
|
|
51
|
+
|
|
52
|
+
content = pyh5p.build(
|
|
53
|
+
"H5P.MultiChoice",
|
|
54
|
+
{
|
|
55
|
+
"question": "What is 2 + 2?",
|
|
56
|
+
"answers": [
|
|
57
|
+
{"text": "4", "correct": True},
|
|
58
|
+
{"text": "5", "correct": False},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
)
|
|
62
|
+
content.save("question.h5p")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The field names and validation rules come from the installed library's
|
|
66
|
+
semantics. The same API works with other runnable libraries, including local,
|
|
67
|
+
historical, and third-party libraries.
|
|
68
|
+
|
|
69
|
+
Media uses the same generic path. For example:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
content = pyh5p.build("H5P.Image", {"decorative": True}, validate=False)
|
|
73
|
+
content.import_media(
|
|
74
|
+
"file", b"image bytes", target="images/example.png", mime="image/png"
|
|
75
|
+
)
|
|
76
|
+
content.save("image.h5p")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`LibraryStore` accepts local library directories and archives. Use it for
|
|
80
|
+
offline builds or for a controlled library set:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
store = pyh5p.LibraryStore("libraries")
|
|
84
|
+
content = pyh5p.build("H5P.MultiChoice", data, store=store, fetch=False)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## What is generic
|
|
88
|
+
|
|
89
|
+
The semantics engine handles groups, lists, nested libraries, text and HTML,
|
|
90
|
+
numbers, booleans, selects, image/video/audio/file fields, defaults, optional
|
|
91
|
+
values, regular expressions, ranges, allowed tags, widgets, `showWhen`,
|
|
92
|
+
`isSubContent`, unknown attributes, and recursive dependencies.
|
|
93
|
+
|
|
94
|
+
It also supports dependency closure for preloaded, dynamic, editor, and
|
|
95
|
+
nested-content libraries. Packages can be loaded, edited, validated, and
|
|
96
|
+
exported again. Builds use stable JSON ordering and ZIP timestamps where the
|
|
97
|
+
H5P format allows it.
|
|
98
|
+
|
|
99
|
+
## Command line
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
pyh5p build H5P.MultiChoice answers.json question.h5p
|
|
103
|
+
pyh5p inspect question.h5p
|
|
104
|
+
pyh5p validate question.h5p
|
|
105
|
+
pyh5p schema H5P.MultiChoice --libraries .pyh5p-libraries -o schema.json
|
|
106
|
+
pyh5p unpack question.h5p unpacked
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Use `--offline` with `build` when network access is not allowed. Use
|
|
110
|
+
`--cache-dir` to choose the library cache. `--content-only` is available when
|
|
111
|
+
the destination explicitly accepts a package without libraries.
|
|
112
|
+
|
|
113
|
+
To preview a package, import it into [Lumi Desktop](https://lumi.education/en/lumi-h5p-offline-desktop-editor/)
|
|
114
|
+
and open its preview. The H5P host runs the library JavaScript; pyh5p creates
|
|
115
|
+
the content and portable package.
|
|
116
|
+
|
|
117
|
+
## JSON Schema and AI applications
|
|
118
|
+
|
|
119
|
+
Ask pyh5p for the schema, pass it to any provider's structured-output API,
|
|
120
|
+
then validate and build the returned object:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
schema = pyh5p.schema_json("H5P.MultiChoice", store=store)
|
|
124
|
+
returned = provider.generate_json(schema=schema)
|
|
125
|
+
content = pyh5p.build("H5P.MultiChoice", returned, store=store)
|
|
126
|
+
content.require_valid()
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The core package has no dependency on an AI provider.
|
|
130
|
+
|
|
131
|
+
## Compatibility and JavaScript hooks
|
|
132
|
+
|
|
133
|
+
Compatibility means that pyh5p parses the supplied definitions, validates the
|
|
134
|
+
semantic data, resolves dependencies, preserves distributable library files,
|
|
135
|
+
and writes the standard H5P package structure. The host must support the Core
|
|
136
|
+
API version declared by the selected libraries. Current Hub packages may
|
|
137
|
+
require Core API 1.28; `--core-api 1.27` selects the historical official Hub
|
|
138
|
+
package set for older hosts.
|
|
139
|
+
|
|
140
|
+
`presave.js` and `upgrades.js` are detected but never executed implicitly.
|
|
141
|
+
They can contain migrations that semantics alone cannot express. Applications
|
|
142
|
+
that need those migrations must provide a sandboxed JavaScript adapter through
|
|
143
|
+
the optional `js` extra, then validate the migrated data.
|
|
144
|
+
|
|
145
|
+
Historical packages can be installed from local sources when the current Hub
|
|
146
|
+
no longer serves their versions. pyh5p does not rewrite a library's declared
|
|
147
|
+
Core API or claim that a newer runtime works on an older host.
|
|
148
|
+
|
|
149
|
+
## Security
|
|
150
|
+
|
|
151
|
+
Archive reading and extraction reject traversal, symlinks, duplicate entries,
|
|
152
|
+
large archives, unsafe compression ratios, and disallowed extensions. Remote
|
|
153
|
+
downloads require HTTPS, an allowlisted host, and public DNS resolution.
|
|
154
|
+
Downloads happen only when fetching is enabled. Library JavaScript is never
|
|
155
|
+
executed by the Python package. Applications that display HTML or media must
|
|
156
|
+
apply their own sanitization and content policy.
|
|
157
|
+
|
|
158
|
+
## Development
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
python -m pip install -e ".[dev]"
|
|
162
|
+
python -m pytest --basetemp .pytest-local
|
|
163
|
+
ruff check .
|
|
164
|
+
mypy src/pyh5p
|
|
165
|
+
python -m build
|
|
166
|
+
python -m twine check dist\*
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The checked-in tests cover semantics, validation paths, dependency graphs,
|
|
170
|
+
nested content, media, round trips, archive security, Hub package selection,
|
|
171
|
+
and malformed input. `scripts/discover_types.py` can fetch an opt-in local
|
|
172
|
+
registry corpus. `scripts/differential_check.py` can run an external validator
|
|
173
|
+
when one is configured. PHP, Lumi, WordPress, and Moodle are not dependencies
|
|
174
|
+
of the package or CI job.
|
|
175
|
+
|
|
176
|
+
Contributions should include a regression test for each fixed bug. Keep the
|
|
177
|
+
core clean-room and do not copy code from the GPL H5P PHP libraries.
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
The project is licensed under the [MIT License](LICENSE) and maintained by
|
|
182
|
+
[Nikita Berger](https://nikity.is-a.dev).
|
|
183
|
+
|
|
184
|
+
H5P is a trademark of H5P Group. pyh5p is independent and is not affiliated
|
|
185
|
+
with or endorsed by H5P Group, H5P.org, Lumi, Moodle, or WordPress. H5P
|
|
186
|
+
libraries retain their own licenses.
|
pyh5p-0.1.0/answers.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Test fixtures
|
|
2
|
+
|
|
3
|
+
`libraries/` contains small synthetic libraries used by the tests. They cover
|
|
4
|
+
the semantic field types, nested library content, dependency classes, media
|
|
5
|
+
fields, `showWhen`, unknown attributes, and JavaScript hook detection.
|
|
6
|
+
|
|
7
|
+
The official registry and Hub change independently of pyh5p releases, and
|
|
8
|
+
third-party repositories carry their own licenses. The discovery tool can
|
|
9
|
+
write an opt-in local corpus without adding those libraries to the repository:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
python scripts/discover_types.py --output fixtures/downloaded --download
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Use the resulting local directories for compatibility and differential runs.
|
|
16
|
+
Do not commit downloaded libraries until their repository and asset licenses
|
|
17
|
+
permit redistribution.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"body": "<strong>Hello</strong><script>unsafe()</script>",
|
|
3
|
+
"nested": {
|
|
4
|
+
"library": "H5P.NestedFixture 1.0",
|
|
5
|
+
"params": {"text": "Nested content"}
|
|
6
|
+
},
|
|
7
|
+
"items": [{"text": "First item"}],
|
|
8
|
+
"options": {"future": "preserved"},
|
|
9
|
+
"unknownAllowed": "visible"
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
H5P.NestedFixture = function () {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.synthetic-fixture { display: block; }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Synthetic semantics fixture",
|
|
3
|
+
"machineName": "H5P.SyntheticAllTypes",
|
|
4
|
+
"majorVersion": 1,
|
|
5
|
+
"minorVersion": 0,
|
|
6
|
+
"patchVersion": 0,
|
|
7
|
+
"runnable": 1,
|
|
8
|
+
"author": "Nikita Berger",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"coreApi": {"majorVersion": 1, "minorVersion": 27},
|
|
11
|
+
"preloadedJs": [{"path": "js/synthetic.js"}],
|
|
12
|
+
"preloadedCss": [{"path": "css/synthetic.css"}],
|
|
13
|
+
"preloadedDependencies": [{"machineName": "H5P.SyntheticDependency", "majorVersion": 1, "minorVersion": 0}],
|
|
14
|
+
"dynamicDependencies": [{"machineName": "H5P.SyntheticDynamic", "majorVersion": 1, "minorVersion": 0}],
|
|
15
|
+
"editorDependencies": [{"machineName": "H5PEditor.SyntheticWidget", "majorVersion": 1, "minorVersion": 0}],
|
|
16
|
+
"futureAttribute": {"preserve": true}
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[
|
|
2
|
+
{"name": "title", "type": "text", "label": "Title", "default": "Example", "maxLength": 80, "regexp": {"pattern": "^[A-Za-z ]+$", "modifiers": "i"}},
|
|
3
|
+
{"name": "body", "type": "text", "label": "HTML body", "widget": "html", "tags": ["strong", "em", "a", "p"]},
|
|
4
|
+
{"name": "count", "type": "number", "label": "Count", "min": 0, "max": 100, "steps": 5, "decimals": 0, "default": 10},
|
|
5
|
+
{"name": "enabled", "type": "boolean", "label": "Enabled", "default": true},
|
|
6
|
+
{"name": "style", "type": "select", "label": "Style", "options": [{"value": "plain", "label": "Plain"}, {"value": "rich", "label": "Rich"}], "default": "plain"},
|
|
7
|
+
{"name": "nested", "type": "library", "label": "Nested activity", "options": ["H5P.NestedFixture 1.0"]},
|
|
8
|
+
{"name": "picture", "type": "image", "label": "Picture", "optional": true, "showWhen": {"rules": [{"field": "enabled", "equals": true}]}},
|
|
9
|
+
{"name": "movies", "type": "video", "label": "Movies", "optional": true},
|
|
10
|
+
{"name": "sounds", "type": "audio", "label": "Sounds", "optional": true},
|
|
11
|
+
{"name": "attachment", "type": "file", "label": "Attachment", "optional": true, "allowedExtensions": ["pdf", "txt"]},
|
|
12
|
+
{"name": "items", "type": "list", "label": "Items", "entity": "item", "min": 1, "max": 3, "defaultNum": 1, "field": {"name": "item", "type": "group", "label": "Item", "isSubContent": true, "fields": [{"name": "text", "type": "text", "label": "Text"}, {"name": "flag", "type": "boolean", "label": "Flag", "default": false}]}},
|
|
13
|
+
{"name": "options", "type": "group", "label": "Options", "expanded": true, "fields": [{"name": "optionalText", "type": "text", "label": "Optional", "optional": true}, {"name": "future", "type": "future-type", "label": "Future", "newAttribute": "keep-me"}]},
|
|
14
|
+
{"name": "unknownAllowed", "type": "text", "label": "Unknown attribute", "widget": "showWhen", "showWhen": {"type": "and", "rules": [{"field": "enabled", "equals": true}], "detach": true, "customRuleAttribute": 7}}
|
|
15
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
H5P.SyntheticDependency = {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
H5P.SyntheticDynamic = {};
|