Skip to content

Commit 68d3b28

Browse files
committed
add a JSON schema for the VDOM spec
1 parent 22e1c81 commit 68d3b28

13 files changed

+291
-13
lines changed

.pre-commit-config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/ambv/black
3-
rev: stable
3+
rev: 20.8b1
44
hooks:
55
- id: black
66
- repo: https://github.com/PyCQA/flake8

docs/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ build
22

33
# VS Code RST extension builds here by default
44
source/_build
5+
source/vdom-json-schema.json
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import json
2+
from pathlib import Path
3+
4+
from sphinx.application import Sphinx
5+
6+
from idom.core.vdom import SERIALIZED_VDOM_JSON_SCHEMA
7+
8+
9+
def setup(app: Sphinx) -> None:
10+
schema_file = Path(__file__).parent.parent / "vdom-json-schema.json"
11+
current_schema = json.dumps(SERIALIZED_VDOM_JSON_SCHEMA, indent=2, sort_keys=True)
12+
13+
# We need to make this check because the autoreload system for the docs checks
14+
# to see if the file has changed to determine whether to re-build. Thus we should
15+
# only write to the file if its contents will be different.
16+
if not schema_file.exists() or schema_file.read_text() != current_schema:
17+
schema_file.write_text(current_schema)

docs/source/conf.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@
4343
"sphinx.ext.napoleon",
4444
"sphinx.ext.autosectionlabel",
4545
"sphinx_autodoc_typehints",
46+
"sphinx_panels",
47+
"sphinx_copybutton",
48+
# custom extensions
4649
"interactive_widget",
4750
"widget_example",
4851
"async_doctest",
49-
"sphinx_panels",
50-
"sphinx_copybutton",
52+
"copy_vdom_json_schema",
5153
]
5254

5355
# Add any paths that contain templates here, relative to this directory.

docs/source/index.rst

+2-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ Libraries for defining and controlling interactive webpages with Python
1212
life-cycle-hooks
1313
core-concepts
1414
javascript-components
15-
15+
specifications
16+
package-api
1617
examples
1718

1819
.. toctree::
@@ -21,8 +22,6 @@ Libraries for defining and controlling interactive webpages with Python
2122

2223
contributing
2324
developer-guide
24-
specifications
25-
package-api
2625

2726
.. toctree::
2827
:hidden:

docs/source/mimetype.json

-3
This file was deleted.

docs/source/specifications.rst

+1-2
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,10 @@ type name. The various properties for the ``onChange`` handler are:
151151

152152
To clearly describe the VDOM schema we've created a `JSON Schema <https://json-schema.org/>`_:
153153

154-
.. literalinclude:: ./mimetype.json
154+
.. literalinclude:: ./vdom-json-schema.json
155155
:language: json
156156

157157

158-
159158
JSON Patch
160159
----------
161160

idom/core/layout.py

+16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import abc
22
import asyncio
3+
from functools import wraps
34
from typing import (
45
Any,
56
AsyncIterator,
@@ -17,10 +18,13 @@
1718
from jsonpatch import apply_patch, make_patch
1819
from loguru import logger
1920

21+
from idom.options import IDOM_DEBUG
22+
2023
from .component import AbstractComponent
2124
from .events import EventHandler, EventTarget
2225
from .hooks import LifeCycleHook
2326
from .utils import CannotAccessResource, HasAsyncResources, async_resource
27+
from .vdom import validate_serialized_vdom
2428

2529

2630
class LayoutUpdate(NamedTuple):
@@ -92,6 +96,18 @@ async def render(self) -> LayoutUpdate:
9296
if self._has_component_state(component):
9397
return self._create_layout_update(component)
9498

99+
if IDOM_DEBUG:
100+
from loguru import logger
101+
102+
_debug_render = render
103+
104+
@wraps(_debug_render)
105+
async def render(self) -> LayoutUpdate:
106+
# Ensure that the model is valid VDOM on each render
107+
result = await self._debug_render()
108+
validate_serialized_vdom(self._component_states[id(self.root)].model)
109+
return result
110+
95111
@async_resource
96112
async def _rendering_queue(self) -> AsyncIterator["_ComponentQueue"]:
97113
queue = _ComponentQueue()

idom/core/vdom.py

+56
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,67 @@
11
from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Tuple, Union
22

3+
from fastjsonschema import compile as compile_json_schema
34
from mypy_extensions import TypedDict
45
from typing_extensions import Protocol
56

67
from .component import AbstractComponent
78
from .events import EventsMapping
89

10+
SERIALIZED_VDOM_JSON_SCHEMA = {
11+
"$schema": "http://json-schema.org/draft-07/schema",
12+
"$ref": "#/definitions/element",
13+
"definitions": {
14+
"element": {
15+
"type": "object",
16+
"properties": {
17+
"tagName": {"type": "string"},
18+
"children": {"$ref": "#/definitions/elementChildren"},
19+
"attributes": {"type": "object"},
20+
"eventHandlers": {"$ref": "#/definitions/elementEventHandlers"},
21+
"importSource": {"$ref": "#/definitions/importSource"},
22+
},
23+
"required": ["tagName"],
24+
},
25+
"elementChildren": {
26+
"type": "array",
27+
"items": {"$ref": "#/definitions/elementOrString"},
28+
},
29+
"elementEventHandlers": {
30+
"type": "object",
31+
"patternProperties": {
32+
".*": {"$ref": "#/definitions/eventHander"},
33+
},
34+
},
35+
"eventHander": {
36+
"type": "object",
37+
"properties": {
38+
"target": {"type": "string"},
39+
"preventDefault": {"type": "boolean"},
40+
"stopPropagation": {"type": "boolean"},
41+
},
42+
"required": ["target"],
43+
},
44+
"importSource": {
45+
"type": "object",
46+
"properties": {
47+
"source": {"type": "string"},
48+
"fallback": {
49+
"if": {"not": {"type": "null"}},
50+
"then": {"$ref": "#/definitions/elementOrString"},
51+
},
52+
},
53+
"required": ["source"],
54+
},
55+
"elementOrString": {
56+
"if": {"not": {"type": "string"}},
57+
"then": {"$ref": "#/definitions/element"},
58+
},
59+
},
60+
}
61+
62+
63+
validate_serialized_vdom = compile_json_schema(SERIALIZED_VDOM_JSON_SCHEMA)
64+
965

1066
class ImportSourceDict(TypedDict):
1167
source: str

idom/options.py

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from typing import Any, Callable, Dict, Type
2+
3+
IDOM_DEBUG = False
4+
5+
6+
def _init() -> None:
7+
"""Collect options from :attr:`os.environ`"""
8+
import os
9+
10+
from_string: Dict[Type[Any], Callable[[Any], Any]] = {
11+
bool: lambda v: bool(int(v)),
12+
}
13+
14+
module = globals()
15+
for name, default in globals().items():
16+
value_type = type(default)
17+
value = os.environ.get(name, default)
18+
if value_type in from_string:
19+
value = from_string[value_type](value)
20+
module[name] = value
21+
22+
return None
23+
24+
25+
_init()

noxfile.py

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def test(session: Session) -> None:
8585
@nox.session
8686
def test_python(session: Session) -> None:
8787
"""Run the Python-based test suite"""
88+
session.env["IDOM_DEBUG"] = "1"
8889
session.install("-r", "requirements/test-env.txt")
8990
session.install(".[all]")
9091
args = ["pytest", "tests"] + get_posargs("pytest", session)

requirements/pkg-deps.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ async_exit_stack >=1.0.1; python_version<"3.7"
77
jsonpatch >=1.26
88
typer >=0.3.2
99
click-spinner >=0.1.10
10-
jsonschema >=3.2.0
10+
fastjsonschema >=2.14.5

0 commit comments

Comments
 (0)