Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2@file 

3@brief Modified version of `bokeh_plot.py <https://github.com/bokeh/bokeh/blob/master/bokeh/sphinxext/bokeh_plot.py>`_, 

4`LICENSE <https://github.com/bokeh/bokeh/blob/master/LICENSE.txt>`_. 

5 

6Include :epkg:`bokeh` plots in Sphinx HTML documentation. 

7For other output types, the placeholder text ``[graph]`` will 

8be generated. 

9The ``bokeh-plot`` directive can be used by either supplying, 

10**a path to a source file** as the argument to the directive:: 

11 

12 .. bokeh-plot:: path/to/plot.py 

13 

14.. note:: 

15 .py scripts are not scanned automatically! In order to include 

16 certain directories into .py scanning process use following directive 

17 in sphinx conf.py file: bokeh_plot_pyfile_include_dirs = ["dir1","dir2"] 

18 

19**Inline code** as the content of the directive:: 

20 

21 .. bokeh-plot:: 

22 

23 from bokeh.plotting import figure, output_file, show 

24 

25 output_file("example.html") 

26 

27 x = [1, 2, 3, 4, 5] 

28 y = [6, 7, 6, 4, 5] 

29 

30 p = figure(title="example", plot_width=300, plot_height=300) 

31 p.line(x, y, line_width=2) 

32 p.circle(x, y, size=10, fill_color="white") 

33 

34 show(p) 

35 

36This directive also works in conjunction with Sphinx autodoc, when 

37used in docstrings. 

38 

39The ``bokeh-plot`` directive accepts the following options: 

40 

41source-position (enum('above', 'below', 'none')): 

42 Where to locate the the block of formatted source 

43 code (if anywhere). 

44 

45linenos (bool): 

46 Whether to display line numbers along with the source. 

47 

48Examples 

49-------- 

50 

51The inline example code above produces the following output: 

52 

53.. bokeh-plot:: 

54 

55 from bokeh.plotting import figure, output_file, show 

56 

57 output_file("example.html") 

58 

59 x = [1, 2, 3, 4, 5] 

60 y = [6, 7, 6, 4, 5] 

61 

62 p = figure(title="example", plot_width=300, plot_height=300) 

63 p.line(x, y, line_width=2) 

64 p.circle(x, y, size=10, fill_color="white") 

65 

66 show(p) 

67""" 

68 

69# ----------------------------------------------------------------------------- 

70# Boilerplate 

71# ----------------------------------------------------------------------------- 

72# use the wrapped sphinx logger 

73from sphinx.util import logging # isort:skip 

74log = logging.getLogger(__name__) 

75 

76# ----------------------------------------------------------------------------- 

77# Imports 

78# ----------------------------------------------------------------------------- 

79 

80# Standard library imports 

81from os import getenv 

82from os.path import basename, dirname, join 

83from uuid import uuid4 

84 

85# External imports 

86from docutils import nodes 

87from docutils.parsers.rst import Directive 

88from docutils.parsers.rst.directives import choice, flag 

89from sphinx.errors import SphinxError 

90from sphinx.util import copyfile, ensuredir, status_iterator 

91from sphinx.util.nodes import set_source_info 

92 

93# Bokeh imports 

94from bokeh.document import Document 

95from bokeh.embed import autoload_static 

96from bokeh.model import Model 

97 

98# Bokeh imports 

99from bokeh.sphinxext.example_handler import ExampleHandler 

100from bokeh.sphinxext.util import get_sphinx_resources 

101 

102# ----------------------------------------------------------------------------- 

103# Globals and constants 

104# ----------------------------------------------------------------------------- 

105 

106__all__ = ( 

107 'BokehPlotDirective', 

108 'setup', 

109) 

110 

111# ----------------------------------------------------------------------------- 

112# General API 

113# ----------------------------------------------------------------------------- 

114 

115 

116class BokehPlotDirective(Directive): 

117 

118 has_content = True 

119 optional_arguments = 2 

120 

121 option_spec = { 

122 'source-position': lambda x: choice(x, ('below', 'above', 'none')), 

123 'linenos': lambda x: True if flag(x) is None else False, 

124 } 

125 

126 def run(self): 

127 

128 env = self.state.document.settings.env 

129 

130 # filename *or* python code content, but not both 

131 if self.arguments and self.content: 

132 raise SphinxError( 

133 "bokeh-plot:: directive can't have both args and content") 

134 

135 # need docname not to look like a path 

136 docname = env.docname.replace("/", "-") 

137 

138 if self.content: 

139 log.debug("[bokeh-plot] handling inline example in %r", env.docname) 

140 path = env.bokeh_plot_auxdir # code runner just needs any real path 

141 source = '\n'.join(self.content) 

142 else: 

143 try: 

144 log.debug("[bokeh-plot] handling external example in %r: %s", 

145 env.docname, self.arguments[0]) 

146 path = self.arguments[0] 

147 if not path.startswith("/"): 

148 path = join(env.app.srcdir, path) 

149 source = open(path).read() 

150 except Exception as e: 

151 raise SphinxError(f"{env.docname}: {e!r}") 

152 

153 js_name = "bokeh-plot-%s-external-%s.js" % (uuid4().hex, docname) 

154 

155 try: 

156 (script, js, js_path, source) = _process_script( 

157 source, path, env, js_name) 

158 except Exception as e: 

159 raise RuntimeError( 

160 "Sphinx bokeh-plot exception: \n\n%s\n\n Failed on:\n\n %s" % (e, source)) 

161 env.bokeh_plot_files[js_name] = ( 

162 script, js, js_path, source, dirname(env.docname)) 

163 

164 # use the source file name to construct a friendly target_id 

165 target_id = "%s.%s" % (env.docname, basename(js_path)) 

166 target = nodes.target('', '', ids=[target_id]) 

167 result = [target] 

168 

169 linenos = self.options.get('linenos', False) 

170 code = nodes.literal_block( 

171 source, source, language="python", linenos=linenos, classes=[]) 

172 set_source_info(self, code) 

173 

174 source_position = self.options.get('source-position', 'below') 

175 

176 if source_position == "above": 

177 result += [code] 

178 

179 result += [nodes.raw('', script, format="html")] 

180 

181 if source_position == "below": 

182 result += [code] 

183 

184 return result 

185 

186# ----------------------------------------------------------------------------- 

187# Dev API 

188# ----------------------------------------------------------------------------- 

189 

190 

191def builder_inited(app): 

192 app.env.bokeh_plot_auxdir = join(app.env.doctreedir, 'bokeh_plot') 

193 if app.env.srcdir is not None and "IMPOSSIBLE:TOFIND" not in app.env.srcdir: 

194 # sphinx/_build/doctrees/bokeh_plot 

195 # sphinx/_build/doctrees/bokeh_plot 

196 ensuredir(app.env.bokeh_plot_auxdir) 

197 

198 if not hasattr(app.env, 'bokeh_plot_files'): 

199 app.env.bokeh_plot_files = {} 

200 

201 

202def build_finished(app, exception): 

203 files = set() 

204 

205 # Sphinx 3.0.0 

206 for docpath, element in app.env.bokeh_plot_files.items(): 

207 if len(element) == 4: 

208 script, js, js_path, source = element 

209 elif len(element) == 5: 

210 script, js, js_path, source, docpath = element 

211 else: 

212 raise ValueError("\n".join([ 

213 str(type(element)), 

214 str(len(element)), 

215 str(element), 

216 ])) from e 

217 files.add((js_path, docpath)) 

218 

219 files_iter = status_iterator(sorted(files), 

220 'copying bokeh-plot files... ', 

221 'brown', 

222 len(files), 

223 app.verbosity, 

224 stringify_func=lambda x: basename(x[0])) 

225 

226 for (file, docpath) in files_iter: 

227 target = join(app.builder.outdir, docpath, basename(file)) 

228 ensuredir(dirname(target)) 

229 try: 

230 copyfile(file, target) 

231 except OSError as e: 

232 raise SphinxError( 

233 'cannot copy local file %r, reason: %s' % (file, e)) 

234 

235 

236def setup(app): 

237 ''' Required Sphinx extension setup function. ''' 

238 app.add_config_value('bokeh_plot_pyfile_include_dirs', [], 'html') 

239 app.add_directive('bokeh-plot', BokehPlotDirective) 

240 app.add_config_value('bokeh_missing_google_api_key_ok', True, 'html') 

241 app.connect('builder-inited', builder_inited) 

242 app.connect('build-finished', build_finished) 

243 

244# ----------------------------------------------------------------------------- 

245# Private API 

246# ----------------------------------------------------------------------------- 

247 

248 

249def _process_script(source, filename, env, js_name, use_relative_paths=False): 

250 # Explicitly make sure old extensions are not included until a better 

251 # automatic mechanism is available 

252 Model._clear_extensions() 

253 

254 # quick and dirty way to inject Google API key 

255 if "GOOGLE_API_KEY" in source: 

256 GOOGLE_API_KEY = getenv('GOOGLE_API_KEY') 

257 if GOOGLE_API_KEY is None: 

258 if env.config.bokeh_missing_google_api_key_ok: 

259 GOOGLE_API_KEY = "MISSING_API_KEY" 

260 else: 

261 raise SphinxError("The GOOGLE_API_KEY environment variable is not set. Set GOOGLE_API_KEY to a valid API key, " 

262 "or set bokeh_missing_google_api_key_ok=True in conf.py to build anyway (with broken GMaps)") 

263 run_source = source.replace("GOOGLE_API_KEY", GOOGLE_API_KEY) 

264 else: 

265 run_source = source 

266 

267 c = ExampleHandler(source=run_source, filename=filename) 

268 d = Document() 

269 c.modify_document(d) 

270 if c.error: 

271 raise RuntimeError(c.error_detail) 

272 

273 resources = get_sphinx_resources() 

274 js_path = join(env.bokeh_plot_auxdir, js_name) 

275 js, script = autoload_static(d.roots[0], resources, js_name) 

276 

277 if "IMPOSSIBLE:TOFIND" not in js_path: 

278 with open(js_path, "w") as f: 

279 f.write(js) 

280 

281 return (script, js, js_path, source) 

282 

283# ----------------------------------------------------------------------------- 

284# Code 

285# -----------------------------------------------------------------------------