diff --git a/nbconvert/exporters/rst.py b/nbconvert/exporters/rst.py index 23061f14e..07984d1e5 100644 --- a/nbconvert/exporters/rst.py +++ b/nbconvert/exporters/rst.py @@ -6,6 +6,7 @@ from traitlets import default from traitlets.config import Config +from ..filters import DataTypeFilter from .templateexporter import TemplateExporter @@ -22,9 +23,24 @@ def _file_extension_default(self): def _template_name_default(self): return "rst" - output_mimetype = "text/restructuredtext" + @default("raw_mimetypes") + def _raw_mimetypes_default(self): + # Up to summer 2024, nbconvert had a mistaken output_mimetype. + # Listing that as an extra option here maintains compatibility for + # notebooks with raw cells marked as that mimetype. + return [self.output_mimetype, "text/restructuredtext", ""] + + output_mimetype = "text/x-rst" export_from_notebook = "reST" + def default_filters(self): + """Override filter_data_type to use native rst outputs""" + dtf = DataTypeFilter() + dtf.display_data_priority = [self.output_mimetype, *dtf.display_data_priority] + filters = dict(super().default_filters()) + filters["filter_data_type"] = dtf + return filters.items() + @property def default_config(self): c = Config( diff --git a/nbconvert/exporters/templateexporter.py b/nbconvert/exporters/templateexporter.py index c6a337d48..42220cbda 100644 --- a/nbconvert/exporters/templateexporter.py +++ b/nbconvert/exporters/templateexporter.py @@ -407,6 +407,7 @@ def from_notebook_node( # type:ignore[explicit-override, override] """ nb_copy, resources = super().from_notebook_node(nb, resources, **kw) resources.setdefault("raw_mimetypes", self.raw_mimetypes) + resources.setdefault("output_mimetype", self.output_mimetype) resources["global_content_filter"] = { "include_code": not self.exclude_code_cell, "include_markdown": not self.exclude_markdown, diff --git a/share/templates/base/display_priority.j2 b/share/templates/base/display_priority.j2 index 27c3c87e9..c24639e9a 100644 --- a/share/templates/base/display_priority.j2 +++ b/share/templates/base/display_priority.j2 @@ -38,6 +38,9 @@ {%- elif type == 'application/vnd.jupyter.widget-view+json' -%} {%- block data_widget_view -%} {%- endblock -%} + {%- elif type == resources.output_mimetype -%} + {%- block data_native -%} + {%- endblock -%} {%- else -%} {%- block data_other -%} {%- endblock -%} diff --git a/share/templates/rst/conf.json b/share/templates/rst/conf.json index cf2f30a0f..f1dd78aa6 100644 --- a/share/templates/rst/conf.json +++ b/share/templates/rst/conf.json @@ -1,6 +1,6 @@ { "base_template": "base", "mimetypes": { - "text/restructuredtext": true + "text/x-rst": true } } diff --git a/share/templates/rst/index.rst.j2 b/share/templates/rst/index.rst.j2 index bda5c5fb9..c374cd240 100644 --- a/share/templates/rst/index.rst.j2 +++ b/share/templates/rst/index.rst.j2 @@ -44,6 +44,10 @@ {{ output.text | indent }} {% endblock stream %} +{% block data_native %} +{{ output.data['text/x-rst'] }} +{% endblock data_native %} + {% block data_svg %} .. image:: {{ output.metadata.filenames['image/svg+xml'] | urlencode }} {% endblock data_svg %} diff --git a/tests/exporters/files/rst_output.ipynb b/tests/exporters/files/rst_output.ipynb new file mode 100644 index 000000000..86dee7d63 --- /dev/null +++ b/tests/exporters/files/rst_output.ipynb @@ -0,0 +1,75 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "d5202f0f-21b8-4509-a9e2-45caf1c7db7a", + "metadata": {}, + "outputs": [], + "source": [ + "from textwrap import indent\n", + "\n", + "\n", + "class Note:\n", + " def __init__(self, text):\n", + " self.text = text\n", + "\n", + " def _repr_html_(self):\n", + " return f'
{self.text}
'\n", + "\n", + " def _repr_mimebundle_(self, include=None, exclude=None):\n", + " return {\"text/x-rst\": \".. note::\\n\\n\" + indent(self.text, \" \")}" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "5145b05f-3a07-4cff-8738-516a9c27cb58", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Testing testing
" + ], + "text/plain": [ + "<__main__.Note at 0x7f457c0250d0>" + ], + "text/x-rst": [ + ".. note::\n", + "\n", + " Testing testing" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Note(\"Testing testing\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/exporters/test_rst.py b/tests/exporters/test_rst.py index 4a5f5c3c4..6e8b24a13 100644 --- a/tests/exporters/test_rst.py +++ b/tests/exporters/test_rst.py @@ -68,3 +68,14 @@ def test_png_metadata(self): assert ":width:" in attr_string assert ":height:" in attr_string assert "px" in attr_string + + def test_rst_output(self): + """ + Is native text/x-rst output included when converting + """ + (output, resources) = RSTExporter().from_filename( + self._get_notebook(nb_name="rst_output.ipynb") + ) + assert len(output) > 0 + assert "\n.. note::" in output + assert ".. raw:: html" not in output # rst should shadow html output