1"""A qt exporter."""
2
3import os
4import sys
5import tempfile
6import time
7
8from traitlets import default
9
10from .html import HTMLExporter
11
12
13class QtExporter(HTMLExporter):
14 """A qt exporter."""
15
16 paginate = None
17 format = ""
18
19 @default("file_extension")
20 def _file_extension_default(self):
21 return ".html"
22
23 def _check_launch_reqs(self):
24 if sys.platform.startswith("win") and self.format == "png":
25 msg = "Exporting to PNG using Qt is currently not supported on Windows."
26 raise RuntimeError(msg)
27 from .qt_screenshot import QT_INSTALLED # noqa: PLC0415
28
29 if not QT_INSTALLED:
30 msg = (
31 f"PyQtWebEngine is not installed to support Qt {self.format.upper()} conversion. "
32 f"Please install `nbconvert[qt{self.format}]` to enable."
33 )
34 raise RuntimeError(msg)
35 from .qt_screenshot import QtScreenshot # noqa: PLC0415
36
37 return QtScreenshot
38
39 def _run_pyqtwebengine(self, html):
40 ext = ".html"
41 temp_file = tempfile.NamedTemporaryFile( # noqa: SIM115
42 suffix=ext, delete=False
43 )
44 filename = f"{temp_file.name[: -len(ext)]}.{self.format}"
45 with temp_file:
46 temp_file.write(html.encode("utf-8"))
47 try:
48 QtScreenshot = self._check_launch_reqs()
49 s = QtScreenshot()
50 s.capture(f"file://{temp_file.name}", filename, self.paginate)
51 finally:
52 # Ensure the file is deleted even if pyqtwebengine raises an exception
53 os.unlink(temp_file.name)
54 # Prefer Qt's in-memory bytes, but fall back to reading the file on disk
55 data = getattr(s, "data", b"")
56
57 if (not data) and os.path.exists(filename):
58 deadline = time.time() + 5.0
59 while time.time() < deadline:
60 try:
61 if os.path.getsize(filename) > 0:
62 break
63 except OSError:
64 pass
65 time.sleep(0.05)
66
67 if os.path.exists(filename) and os.path.getsize(filename) > 0:
68 with open(filename, "rb") as f:
69 data = f.read()
70
71 # Best-effort cleanup of the generated output file
72 try:
73 if os.path.exists(filename):
74 os.unlink(filename)
75 except OSError:
76 pass
77
78 return data
79
80 def from_notebook_node(self, nb, resources=None, **kw):
81 """Convert from notebook node."""
82 self._check_launch_reqs()
83 html, resources = super().from_notebook_node(nb, resources=resources, **kw)
84
85 self.log.info("Building %s", self.format.upper())
86 data = self._run_pyqtwebengine(html)
87 self.log.info("%s successfully created", self.format.upper())
88
89 # convert output extension
90 # the writer above required it to be html
91 resources["output_extension"] = f".{self.format}"
92
93 return data, resources