1"""Module containing single call export functions."""
2
3# Copyright (c) Jupyter Development Team.
4# Distributed under the terms of the Modified BSD License.
5
6import os
7import sys
8
9if sys.version_info < (3, 10):
10 from importlib_metadata import entry_points # type:ignore[import-not-found]
11else:
12 from importlib.metadata import entry_points
13from nbformat import NotebookNode
14from traitlets.config import get_config
15from traitlets.log import get_logger
16from traitlets.utils.importstring import import_item
17
18from .exporter import Exporter
19
20# -----------------------------------------------------------------------------
21# Functions
22# -----------------------------------------------------------------------------
23
24__all__ = [
25 "export",
26 "Exporter",
27 "get_exporter",
28 "get_export_names",
29 "ExporterNameError",
30]
31
32
33class ExporterNameError(NameError):
34 """An exporter name error."""
35
36
37class ExporterDisabledError(ValueError):
38 """An exporter disabled error."""
39
40
41def export(exporter, nb, **kw):
42 """
43 Export a notebook object using specific exporter class.
44
45 Parameters
46 ----------
47 exporter : ``Exporter`` class or instance
48 Class or instance of the exporter that should be used. If the
49 method initializes its own instance of the class, it is ASSUMED that
50 the class type provided exposes a constructor (``__init__``) with the same
51 signature as the base Exporter class.
52 nb : :class:`~nbformat.NotebookNode`
53 The notebook to export.
54 config : config (optional, keyword arg)
55 User configuration instance.
56 resources : dict (optional, keyword arg)
57 Resources used in the conversion process.
58
59 Returns
60 -------
61 tuple
62 output : str
63 The resulting converted notebook.
64 resources : dictionary
65 Dictionary of resources used prior to and during the conversion
66 process.
67 """
68
69 # Check arguments
70 if exporter is None:
71 msg = "Exporter is None"
72 raise TypeError(msg)
73 if not isinstance(exporter, Exporter) and not issubclass(exporter, Exporter):
74 msg = "exporter does not inherit from Exporter (base)"
75 raise TypeError(msg)
76 if nb is None:
77 msg = "nb is None"
78 raise TypeError(msg)
79
80 # Create the exporter
81 resources = kw.pop("resources", None)
82 exporter_instance = exporter if isinstance(exporter, Exporter) else exporter(**kw)
83
84 # Try to convert the notebook using the appropriate conversion function.
85 if isinstance(nb, NotebookNode):
86 output, resources = exporter_instance.from_notebook_node(nb, resources)
87 elif isinstance(nb, (str,)):
88 output, resources = exporter_instance.from_filename(nb, resources)
89 else:
90 output, resources = exporter_instance.from_file(nb, resources)
91 return output, resources
92
93
94def get_exporter(name, config=None):
95 """Given an exporter name or import path, return a class ready to be instantiated
96
97 Raises ExporterName if exporter is not found or ExporterDisabledError if not enabled
98 """
99
100 if config is None:
101 config = get_config()
102
103 if name == "ipynb":
104 name = "notebook"
105
106 try:
107 exporters = entry_points(group="nbconvert.exporters")
108 items = [e for e in exporters if e.name == name or e.name == name.lower()]
109 exporter = items[0].load()
110 if getattr(exporter(config=config), "enabled", True):
111 return exporter
112 raise ExporterDisabledError('Exporter "%s" disabled in configuration' % (name))
113 except IndexError:
114 pass
115
116 if "." in name:
117 try:
118 exporter = import_item(name)
119 if getattr(exporter(config=config), "enabled", True):
120 return exporter
121 raise ExporterDisabledError('Exporter "%s" disabled in configuration' % (name))
122 except ImportError:
123 log = get_logger()
124 log.error("Error importing %s", name, exc_info=True) # noqa: G201
125
126 msg = 'Unknown exporter "{}", did you mean one of: {}?'.format(
127 name, ", ".join(get_export_names())
128 )
129 raise ExporterNameError(msg)
130
131
132def get_export_names(config=None):
133 """Return a list of the currently supported export targets
134
135 Exporters can be found in external packages by registering
136 them as an nbconvert.exporter entrypoint.
137 """
138
139 exporters = sorted(e.name for e in entry_points(group="nbconvert.exporters"))
140 if os.environ.get("NBCONVERT_DISABLE_CONFIG_EXPORTERS"):
141 get_logger().info(
142 "Config exporter loading disabled, no additional exporters will be automatically included."
143 )
144 return exporters
145
146 if config is None:
147 config = get_config()
148
149 enabled_exporters = []
150 for exporter_name in exporters:
151 try:
152 e = get_exporter(exporter_name)(config=config)
153 if e.enabled:
154 enabled_exporters.append(exporter_name)
155 except (ExporterDisabledError, ValueError):
156 pass
157 return enabled_exporters