omle-viewer 0.1.0__py3-none-any.whl
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.
Potentially problematic release.
This version of omle-viewer might be problematic. Click here for more details.
- omle_viewer/__init__.py +10 -0
- omle_viewer/_version.py +24 -0
- omle_viewer/cli.py +55 -0
- omle_viewer/display.py +172 -0
- omle_viewer/static/viewer.html +81 -0
- omle_viewer-0.1.0.dist-info/METADATA +120 -0
- omle_viewer-0.1.0.dist-info/RECORD +11 -0
- omle_viewer-0.1.0.dist-info/WHEEL +5 -0
- omle_viewer-0.1.0.dist-info/entry_points.txt +2 -0
- omle_viewer-0.1.0.dist-info/licenses/LICENSE +201 -0
- omle_viewer-0.1.0.dist-info/top_level.txt +1 -0
omle_viewer/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from .display import OMLEDisplay, model_repr_html, show, show_in_browser
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from ._version import (
|
|
5
|
+
__version__, # generated by setuptools-scm at build time
|
|
6
|
+
)
|
|
7
|
+
except ImportError: # pragma: no cover - source checkout without a build
|
|
8
|
+
__version__ = "0.0.0+unknown"
|
|
9
|
+
|
|
10
|
+
__all__ = ["OMLEDisplay", "show", "show_in_browser", "model_repr_html", "__version__"]
|
omle_viewer/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
omle_viewer/cli.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""CLI entry point: ``omle-viewer <model-file>``
|
|
2
|
+
|
|
3
|
+
Opens an OMLE model file in the default web browser using the interactive
|
|
4
|
+
DAG viewer. Supports JSON (.json) and protobuf binary (.omle, .pb, .bin)
|
|
5
|
+
file formats.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main() -> None:
|
|
16
|
+
parser = argparse.ArgumentParser(
|
|
17
|
+
prog="omle-viewer",
|
|
18
|
+
description="Open an OMLE model in the interactive DAG viewer.",
|
|
19
|
+
)
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"model",
|
|
22
|
+
metavar="MODEL_FILE",
|
|
23
|
+
help="Path to the model file (.json, .omle, .pb, .bin)",
|
|
24
|
+
)
|
|
25
|
+
args = parser.parse_args()
|
|
26
|
+
|
|
27
|
+
path = Path(args.model)
|
|
28
|
+
if not path.exists():
|
|
29
|
+
print(f"omle-viewer: file not found: {path}", file=sys.stderr)
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
import omle
|
|
34
|
+
except ImportError:
|
|
35
|
+
print(
|
|
36
|
+
"omle-viewer: the 'omle' package is required. "
|
|
37
|
+
"Install it with: pip install omle",
|
|
38
|
+
file=sys.stderr,
|
|
39
|
+
)
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
|
|
42
|
+
# omle.load dispatches on the extension and decodes both encodings; omle
|
|
43
|
+
# depends on protobuf directly, so binary files need no optional extra.
|
|
44
|
+
try:
|
|
45
|
+
model = omle.load(path)
|
|
46
|
+
except ValueError as e:
|
|
47
|
+
print(f"omle-viewer: {e}", file=sys.stderr)
|
|
48
|
+
sys.exit(1)
|
|
49
|
+
|
|
50
|
+
from .display import show_in_browser
|
|
51
|
+
show_in_browser(model)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if __name__ == "__main__":
|
|
55
|
+
main()
|
omle_viewer/display.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Jupyter display helpers for OMLE models.
|
|
2
|
+
|
|
3
|
+
Primary approach: self-contained HTML iframe (works in every Jupyter environment
|
|
4
|
+
without any widget extensions — JupyterLab, Classic Notebook, VS Code, Colab).
|
|
5
|
+
|
|
6
|
+
The viewer HTML is built by running ``./build.sh`` at the repo root and embedded
|
|
7
|
+
as a base64 data-URI iframe so it needs no external server or asset loading.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from omle.ir.model import OMLEModel
|
|
18
|
+
|
|
19
|
+
_STATIC = Path(__file__).parent / "static"
|
|
20
|
+
_VIEWER_HTML = _STATIC / "viewer.html"
|
|
21
|
+
|
|
22
|
+
# ── HTML helpers ──────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
_NOT_BUILT_HTML = """
|
|
25
|
+
<div style="padding:14px 16px;font-family:monospace;font-size:12px;
|
|
26
|
+
color:#b00;border:1px solid #f5c0c0;border-radius:6px;background:#fff5f5">
|
|
27
|
+
<strong>omle-viewer:</strong> The viewer bundle has not been built yet.<br><br>
|
|
28
|
+
Run the following from the repo root, then reinstall the package:<br>
|
|
29
|
+
<code style="display:block;margin-top:6px;padding:6px 8px;background:#fff;
|
|
30
|
+
border:1px solid #ddd;border-radius:4px">
|
|
31
|
+
./build.sh<br>
|
|
32
|
+
pip install -e omle-viewer/python/
|
|
33
|
+
</code>
|
|
34
|
+
</div>
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _build_viewer_html(model: OMLEModel) -> str | None:
|
|
39
|
+
"""Return the full self-contained viewer HTML with the model pre-loaded, or None if not built."""
|
|
40
|
+
if not _VIEWER_HTML.exists():
|
|
41
|
+
return None
|
|
42
|
+
viewer = _VIEWER_HTML.read_text(encoding="utf-8")
|
|
43
|
+
model_json = json.dumps(model.to_dict())
|
|
44
|
+
inject = f"<script>window.__OMLE_MODEL__ = {model_json};</script>"
|
|
45
|
+
return viewer.replace("</head>", inject + "</head>", 1)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _build_iframe_html(model: OMLEModel, height: int) -> str:
|
|
49
|
+
"""Return an ``<iframe srcdoc=...>`` HTML string that embeds the full interactive viewer.
|
|
50
|
+
|
|
51
|
+
Uses srcdoc instead of a data: URI because JupyterLab's CSP blocks data: iframes.
|
|
52
|
+
"""
|
|
53
|
+
viewer = _build_viewer_html(model)
|
|
54
|
+
if viewer is None:
|
|
55
|
+
return _NOT_BUILT_HTML
|
|
56
|
+
import html as _html
|
|
57
|
+
# srcdoc is same-origin with the parent page so CSP `unsafe-inline` covers it;
|
|
58
|
+
# data: URIs are blocked by JupyterLab's frame-src CSP directive.
|
|
59
|
+
encoded = _html.escape(viewer, quote=True)
|
|
60
|
+
return (
|
|
61
|
+
f'<iframe srcdoc="{encoded}" '
|
|
62
|
+
f'style="width:100%;height:{height}px;border:none;border-radius:4px;" '
|
|
63
|
+
f'allowfullscreen>'
|
|
64
|
+
f"</iframe>"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def show_in_browser(model: OMLEModel) -> None:
|
|
69
|
+
"""Open the interactive DAG viewer for *model* in the default web browser.
|
|
70
|
+
|
|
71
|
+
Writes a temporary self-contained HTML file and opens it via
|
|
72
|
+
``webbrowser.open()``. Works from a script, a terminal, or a notebook.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
model:
|
|
77
|
+
The OMLEModel instance to visualize.
|
|
78
|
+
"""
|
|
79
|
+
import tempfile
|
|
80
|
+
import webbrowser
|
|
81
|
+
|
|
82
|
+
viewer = _build_viewer_html(model)
|
|
83
|
+
if viewer is None:
|
|
84
|
+
raise RuntimeError(
|
|
85
|
+
"omle-viewer: viewer bundle not built. "
|
|
86
|
+
"Run ./build.sh at the repo root first."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
with tempfile.NamedTemporaryFile(
|
|
90
|
+
suffix=".html", delete=False, mode="w", encoding="utf-8"
|
|
91
|
+
) as f:
|
|
92
|
+
f.write(viewer)
|
|
93
|
+
tmp_path = f.name
|
|
94
|
+
|
|
95
|
+
webbrowser.open(f"file://{tmp_path}")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ── Public API ────────────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
class OMLEDisplay:
|
|
101
|
+
"""Jupyter display object wrapping an OMLEModel.
|
|
102
|
+
|
|
103
|
+
Returned by ``show()`` so callers have a reference, but suppresses the
|
|
104
|
+
cell's auto-display of the return value (the iframe was already shown by
|
|
105
|
+
the explicit ``IPython.display.display()`` call inside ``show()``).
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def __init__(self, model: OMLEModel, height: int = 500) -> None:
|
|
109
|
+
self._model = model
|
|
110
|
+
self._height = height
|
|
111
|
+
|
|
112
|
+
# Suppress Jupyter's implicit display of the return value.
|
|
113
|
+
def _repr_mimebundle_(self, **_kwargs):
|
|
114
|
+
return {}
|
|
115
|
+
|
|
116
|
+
def __repr__(self) -> str: # plain-Python fallback
|
|
117
|
+
return "<OMLEDisplay — call .show() to redisplay>"
|
|
118
|
+
|
|
119
|
+
def show(self, height: int | None = None) -> None:
|
|
120
|
+
"""Re-display the viewer (e.g. after calling .update())."""
|
|
121
|
+
try:
|
|
122
|
+
import warnings
|
|
123
|
+
|
|
124
|
+
from IPython.display import HTML, display
|
|
125
|
+
with warnings.catch_warnings():
|
|
126
|
+
warnings.simplefilter("ignore", UserWarning)
|
|
127
|
+
display(HTML(_build_iframe_html(self._model, height or self._height)))
|
|
128
|
+
except ImportError:
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
def update(self, model: OMLEModel, *, height: int | None = None) -> None:
|
|
132
|
+
"""Display an updated model in a new cell output."""
|
|
133
|
+
self._model = model
|
|
134
|
+
if height is not None:
|
|
135
|
+
self._height = height
|
|
136
|
+
self.show()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def model_repr_html(model: OMLEModel, height: int = 500) -> str:
|
|
140
|
+
"""Return the ``_repr_html_`` string for *model* (used by the display hook)."""
|
|
141
|
+
return _build_iframe_html(model, height)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def show(model: OMLEModel, height: int = 500) -> OMLEDisplay:
|
|
145
|
+
"""Display an OMLEModel DAG inline in a Jupyter notebook.
|
|
146
|
+
|
|
147
|
+
Importing ``omle_viewer`` already makes models auto-render when
|
|
148
|
+
returned from a cell, so ``show()`` is mainly useful when you want a
|
|
149
|
+
non-default height or a handle for later updates::
|
|
150
|
+
|
|
151
|
+
w = show(model, height=700)
|
|
152
|
+
# push a new model without creating a new cell output:
|
|
153
|
+
w.update(updated_model)
|
|
154
|
+
|
|
155
|
+
Parameters
|
|
156
|
+
----------
|
|
157
|
+
model:
|
|
158
|
+
The OMLEModel instance to visualize.
|
|
159
|
+
height:
|
|
160
|
+
Viewer height in pixels (default 500).
|
|
161
|
+
"""
|
|
162
|
+
handle = OMLEDisplay(model, height)
|
|
163
|
+
try:
|
|
164
|
+
import warnings
|
|
165
|
+
|
|
166
|
+
from IPython.display import HTML, display
|
|
167
|
+
with warnings.catch_warnings():
|
|
168
|
+
warnings.simplefilter("ignore", UserWarning)
|
|
169
|
+
display(HTML(_build_iframe_html(model, height)))
|
|
170
|
+
except ImportError:
|
|
171
|
+
pass
|
|
172
|
+
return handle
|