Skip to content

Commit beeb165

Browse files
committed
Organize _kaleido_tab exports.
1 parent cd6b471 commit beeb165

2 files changed

Lines changed: 181 additions & 173 deletions

File tree

Lines changed: 8 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -1,173 +1,8 @@
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+
]
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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

0 commit comments

Comments
 (0)