|
1 | | -from __future__ import annotations |
2 | | - |
3 | | -import base64 |
4 | | -from typing import TYPE_CHECKING |
5 | | - |
6 | | -import logistro |
7 | | - |
8 | | -from kaleido._utils import to_thread |
9 | | - |
10 | | -from . import _devtools_utils as _dtools |
11 | | -from . import _js_logger |
12 | | -from ._errors import _raise_error |
13 | | - |
14 | | -if TYPE_CHECKING: |
15 | | - import asyncio |
16 | | - from pathlib import Path |
17 | | - |
18 | | - import choreographer as choreo |
19 | | - |
20 | | - from kaleido import _fig_tools |
21 | | - |
22 | | - |
23 | | -_TEXT_FORMATS = ("svg", "json") # eps |
24 | | - |
25 | | -_logger = logistro.getLogger(__name__) |
26 | | - |
27 | | - |
28 | | -def _subscribe_new(tab: choreo.Tab, event: str) -> asyncio.Future: |
29 | | - """Create subscription to tab clearing old ones first: helper function.""" |
30 | | - new_future = tab.subscribe_once(event) |
31 | | - while new_future.done(): |
32 | | - _logger.debug2(f"Clearing an old {event}") |
33 | | - new_future = tab.subscribe_once(event) |
34 | | - return new_future |
35 | | - |
36 | | - |
37 | | -class _KaleidoTab: |
38 | | - """ |
39 | | - A Kaleido tab is a wrapped choreographer tab providing the functions we need. |
40 | | -
|
41 | | - The choreographer tab can be accessed through the `self.tab` attribute. |
42 | | - """ |
43 | | - |
44 | | - tab: choreo.Tab |
45 | | - """The underlying choreographer tab.""" |
46 | | - js_logger: _js_logger.JavascriptLogger |
47 | | - """A log for recording javascript.""" |
48 | | - |
49 | | - def __init__(self, tab, *, _stepper=False): |
50 | | - """ |
51 | | - Create a new _KaleidoTab. |
52 | | -
|
53 | | - Args: |
54 | | - tab: the choreographer tab to wrap. |
55 | | -
|
56 | | - """ |
57 | | - self.tab = tab |
58 | | - self.js_logger = _js_logger.JavascriptLogger(self.tab) |
59 | | - self._stepper = _stepper |
60 | | - |
61 | | - async def navigate(self, url: str | Path = ""): |
62 | | - """ |
63 | | - Navigate to the kaleidofier script. This is effectively the real initialization. |
64 | | -
|
65 | | - Args: |
66 | | - url: Override the location of the kaleidofier script if necessary. |
67 | | -
|
68 | | - """ |
69 | | - # Subscribe to event which will contain javascript engine ID (need it |
70 | | - # for calling javascript functions) |
71 | | - javascript_ready = _subscribe_new(self.tab, "Runtime.executionContextCreated") |
72 | | - |
73 | | - # Subscribe to event indicating page ready. |
74 | | - page_ready = _subscribe_new(self.tab, "Page.loadEventFired") |
75 | | - |
76 | | - # Navigating page. This will trigger the above events. |
77 | | - _logger.debug2(f"Calling Page.navigate on {self.tab}") |
78 | | - _raise_error(await self.tab.send_command("Page.navigate", params={"url": url})) |
79 | | - |
80 | | - # Enabling page events (for page_ready- like all events, if already |
81 | | - # ready, the latest will fire immediately) |
82 | | - _logger.debug2(f"Calling Page.enable on {self.tab}") |
83 | | - _raise_error(await self.tab.send_command("Page.enable")) |
84 | | - |
85 | | - # Enabling javascript events (for javascript_ready) |
86 | | - _logger.debug2(f"Calling Runtime.enable on {self.tab}") |
87 | | - _raise_error(await self.tab.send_command("Runtime.enable")) |
88 | | - |
89 | | - self._current_js_id = _dtools.get_js_id(await javascript_ready) |
90 | | - |
91 | | - await page_ready # don't care result, ready is ready |
92 | | - |
93 | | - # might miss console stuff, would need to |
94 | | - # catch an intermediate event and run it via callback |
95 | | - # catches all render errors though |
96 | | - self.js_logger.reset() |
97 | | - |
98 | | - # reload is truly so close to navigate |
99 | | - async def reload(self): |
100 | | - """Reload the tab, and set the javascript runtime id.""" |
101 | | - _logger.debug(f"Reloading tab {self.tab} with javascript.") |
102 | | - |
103 | | - javascript_ready = _subscribe_new(self.tab, "Runtime.executionContextCreated") |
104 | | - |
105 | | - page_ready = _subscribe_new(self.tab, "Page.loadEventFired") |
106 | | - |
107 | | - _logger.debug2(f"Calling Page.reload on {self.tab}") |
108 | | - _raise_error(await self.tab.send_command("Page.reload")) |
109 | | - |
110 | | - self._current_js_id = _dtools.get_js_id(await javascript_ready) |
111 | | - |
112 | | - await page_ready |
113 | | - |
114 | | - self.js_logger.reset() |
115 | | - |
116 | | - async def _calc_fig( |
117 | | - self, |
118 | | - spec: _fig_tools.Spec, |
119 | | - full_path: Path, |
120 | | - *, |
121 | | - topojson: str | None = None, |
122 | | - **_kwargs, |
123 | | - ): |
124 | | - _kwargs.pop("error_log", None) # not used at the moment |
125 | | - _kwargs.pop("profiler", None) # not used at the moment |
126 | | - |
127 | | - _logger.debug(f"In tab {self.tab.target_id[:4]} calc_fig for {full_path.name}.") |
128 | | - _logger.info(f"Processing {full_path.name}") |
129 | | - |
130 | | - # js script |
131 | | - kaleido_js_fn = ( |
132 | | - r"function(spec, ...args)" |
133 | | - r"{" |
134 | | - r"return kaleido_scopes.plotly(spec, ...args).then(JSON.stringify);" |
135 | | - r"}" |
136 | | - ) |
137 | | - |
138 | | - _logger.info(f"Sending big command for {full_path.name}.") |
139 | | - result = await _dtools.exec_js_fn( |
140 | | - self.tab, |
141 | | - self._current_js_id, |
142 | | - kaleido_js_fn, |
143 | | - spec, |
144 | | - topojson, |
145 | | - self._stepper, |
146 | | - ) |
147 | | - _raise_error(result) |
148 | | - |
149 | | - # TODO(AJP): better define these error mechanics, is this a devtools |
150 | | - # function or what |
151 | | - # upon implementation of error, might not be necessary |
152 | | - # to do these go-lang/c style returns |
153 | | - # but we have to collect and associate |
154 | | - # with the gather + profile |
155 | | - # None-non return values are a problem |
156 | | - # In general, need to better understand stuff here |
157 | | - |
158 | | - _logger.debug2(f"Result of function call: {result}") |
159 | | - js_response, error = _dtools.check_kaleido_js_response(result) |
160 | | - if error: |
161 | | - raise error |
162 | | - |
163 | | - if (response_format := js_response.get("format")) == "pdf": |
164 | | - img_raw, error = await _dtools.print_pdf(self.tab) |
165 | | - else: |
166 | | - img_raw = js_response.get("result") # type: ignore[assignment] |
167 | | - if error: |
168 | | - raise error |
169 | | - |
170 | | - if response_format not in _TEXT_FORMATS: |
171 | | - return base64.b64decode(img_raw), None |
172 | | - else: |
173 | | - return str.encode(img_raw), None |
| 1 | +from ._errors import JavascriptError, KaleidoError |
| 2 | +from ._tab import _KaleidoTab |
| 3 | + |
| 4 | +__all__ = [ |
| 5 | + "JavascriptError", |
| 6 | + "KaleidoError", |
| 7 | + "_KaleidoTab", |
| 8 | +] |
0 commit comments