Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create entrypoints #352

Merged
merged 14 commits into from
Aug 29, 2016
Merged
101 changes: 38 additions & 63 deletions nbconvert/exporters/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
# Distributed under the terms of the Modified BSD License.

from functools import wraps
from itertools import chain
import warnings

import entrypoints

Expand All @@ -25,59 +27,12 @@
from .notebook import NotebookExporter
from .script import ScriptExporter

#-----------------------------------------------------------------------------
# Classes
#-----------------------------------------------------------------------------

def DocDecorator(f):

#Set docstring of function
f.__doc__ = f.__doc__ + """

nb : :class:`~nbformat.NotebookNode`
The notebook to export.
config : config (optional, keyword arg)
User configuration instance.
resources : dict (optional, keyword arg)
Resources used in the conversion process.

Returns
-------
tuple
output : str
Jinja 2 output. This is the resulting converted notebook.
resources : dictionary
Dictionary of resources used prior to and during the conversion
process.
exporter_instance : Exporter
Instance of the Exporter class used to export the document. Useful
to caller because it provides a 'file_extension' property which
specifies what extension the output should be saved as.

"""

@wraps(f)
def decorator(*args, **kwargs):
return f(*args, **kwargs)

return decorator


#-----------------------------------------------------------------------------
# Functions
#-----------------------------------------------------------------------------

__all__ = [
'export',
'export_html',
'export_custom',
'export_slides',
'export_latex',
'export_pdf',
'export_markdown',
'export_python',
'export_script',
'export_rst',
'export_by_name',
'get_export_names',
'ExporterNameError',
Expand All @@ -87,7 +42,6 @@ def decorator(*args, **kwargs):
class ExporterNameError(NameError):
pass

@DocDecorator
def export(exporter, nb, **kw):
"""
Export a notebook object using specific exporter class.
Expand All @@ -99,6 +53,26 @@ def export(exporter, nb, **kw):
method initializes it's own instance of the class, it is ASSUMED that
the class type provided exposes a constructor (``__init__``) with the same
signature as the base Exporter class.
nb : :class:`~nbformat.NotebookNode`
The notebook to export.
config : config (optional, keyword arg)
User configuration instance.
resources : dict (optional, keyword arg)
Resources used in the conversion process.

Returns
-------
tuple
output : str
Jinja 2 output. This is the resulting converted notebook.
resources : dictionary
Dictionary of resources used prior to and during the conversion
process.
exporter_instance : Exporter
Instance of the Exporter class used to export the document. Useful
to caller because it provides a 'file_extension' property which
specifies what extension the output should be saved as.

"""

#Check arguments
Expand Down Expand Up @@ -148,12 +122,13 @@ def _export(nb, **kw):
g = globals()

for name, E in exporter_map.items():
g['export_%s' % name] = DocDecorator(_make_exporter(name, E))
g['export_%s' % name] = _make_exporter(name, E)


@DocDecorator
def export_by_name(format_name, nb, **kw):
"""
Deprecated since version 5.0.

Export a notebook object to a template type by its name. Reflection
(Inspect) is used to find the template's corresponding explicit export
method defined in this module. That method is then called directly.
Expand All @@ -164,30 +139,30 @@ def export_by_name(format_name, nb, **kw):
Name of the template style to export to.
"""

exporter = get_exporter(name)
function_name = "export_" + format_name.lower()

if function_name in globals():
return globals()[function_name](nb, **kw)
else:
exporter = get_exporter(name)

raise ExporterNameError("Exporter for `%s` not found" % function_name)
warnings.warn("export_by_name is deprecated since nbconvert 5.0. Instead, use export(get_exporter(format_name), nb, **kw)).", DeprecationWarning, stacklevel=2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring should indicate that this is deprecated as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cf40f27 addresses this


try:
Exporter = get_exporter(format_name)
return export(Exporter, nb, **kw)
except ValueError:
raise ExporterNameError("Exporter for `%s` not found" % format_name)



def get_exporter(name):
""" given an exporter name, return a class ready to be instantiated

Raises ValueError if exporter is not found
"""
if name.lower() in exporter_map:
return exporter_map[name.lower()]

try:
return entrypoints.get_single('nbconvert.exporters', name).load()
except entrypoints.NoSuchEntryPoint:
pass

try:
return entrypoints.get_single('nbconvert.exporters', name.lower()).load()
except entrypoints.NoSuchEntryPoint:
pass

if '.' in name:
try:
return import_item(name)
Expand Down
8 changes: 2 additions & 6 deletions nbconvert/exporters/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,9 @@ def from_notebook_node(self, nb, resources=None, **kw):
exporter_name = langinfo.get('nbconvert_exporter')
if exporter_name and exporter_name != 'script':
self.log.debug("Loading script exporter: %s", exporter_name)
from .export import exporter_map
from .export import get_exporter
if exporter_name not in self._exporters:
if exporter_name in exporter_map:
Exporter = exporter_map[exporter_name]
else:
self.log.debug("Importing custom Exporter: %s", exporter_name)
Exporter = import_item(exporter_name)
Exporter = get_exporter(exporter_name)
self._exporters[exporter_name] = Exporter(parent=self)
exporter = self._exporters[exporter_name]
return exporter.from_notebook_node(nb, resources, **kw)
Expand Down
31 changes: 27 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
#-----------------------------------------------------------------------------

import os
import setuptools

from setuptools.command.bdist_egg import bdist_egg

from glob import glob
from io import BytesIO
try:
Expand Down Expand Up @@ -122,6 +126,16 @@ def run(self):

cmdclass = {'css': FetchCSS}

class bdist_egg_disabled(bdist_egg):
"""Disabled version of bdist_egg

Prevents setup.py install performing setuptools' default easy_install,
which it should never ever do.
"""
def run(self):
sys.exit("Aborting implicit building of eggs. Use `pip install .` to install from source.")


def css_first(command):
class CSSFirst(command):
def run(self):
Expand All @@ -131,6 +145,7 @@ def run(self):

cmdclass['build'] = css_first(build)
cmdclass['sdist'] = css_first(sdist)
cmdclass['bdist_egg'] = bdist_egg if 'bdist_egg' in sys.argv else bdist_egg_disabled
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@minrk - just noticed that this and Jupyterlab both have a conditional to allow explicit building of eggs. Is there a reason that's important, or are we just anticipating the possibility that someone may want it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To paraphrase one of the PyPA folks, I was avoiding being "hostile to particular workflows". If it's important to someone to build an egg, it's fine by me, I just want to avoid the implicit egg caused by setup.py install. I figure there's no harm in the extra few characters to allow explicitly requested eggs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I support the manual inclusion too. Presumably people who explicitly want an egg know why they want it, and we're more likely to get complaints for restricting it than we are to get issues from people who "accidentally" manually built an egg and accordingly ran into problems.


for d, _, _ in os.walk(pjoin(pkg_root, 'templates')):
g = pjoin(d[len(pkg_root)+1:], '*.*')
Expand Down Expand Up @@ -168,9 +183,6 @@ def run(self):
],
)

if 'develop' in sys.argv or any(a.startswith('bdist') for a in sys.argv):
import setuptools

setuptools_args = {}
install_requires = setuptools_args['install_requires'] = [
'mistune!=0.6',
Expand All @@ -197,7 +209,18 @@ def run(self):
setup_args['entry_points'] = {
'console_scripts': [
'jupyter-nbconvert = nbconvert.nbconvertapp:main',
]
],
"nbconvert.exporters" : [
'custom=nbconvert.exporters:TemplateExporter',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is TemplateExporter usable directly? How much extra info do you have to supply at the command line to use it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes and no. The custom exporter is configurable in regular config-files. So you can just use --to custom and it will pick up the rest of its configuration. It should mostly be replaced by fully qualified exporter names, and entry points now.

'html=nbconvert.exporters:HTMLExporter',
'slides=nbconvert.exporters:SlidesExporter',
'latex=nbconvert.exporters:LatexExporter',
'pdf=nbconvert.exporters:PDFExporter',
'markdown=nbconvert.exporters:MarkdownExporter',
'python=nbconvert.exporters:PythonExporter',
'rst=nbconvert.exporters:RSTExporter',
'notebook=nbconvert.exporters:NotebookExporter',
'script=nbconvert.exporters:ScriptExporter']
}
setup_args.pop('scripts', None)

Expand Down