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.

@@ -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__"]
@@ -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