broadinstitute / CellBender

CellBender is a software package for eliminating technical artifacts from high-throughput single-cell RNA sequencing (scRNA-seq) data.
https://cellbender.rtfd.io
BSD 3-Clause "New" or "Revised" License
271 stars 50 forks source link

HTML output fails due to lxml change #348

Closed sjfleming closed 2 months ago

sjfleming commented 3 months ago

Jupyter's nbconvert, which we use to generate an HTML report from jupyter notebook template, recently runs into

Traceback (most recent call last):
  File "/home/sfleming/miniforge3/envs/cellbender/bin/jupyter-nbconvert", line 5, in <module>
    from nbconvert.nbconvertapp import main
  File "/home/sfleming/miniforge3/envs/cellbender/lib/python3.7/site-packages/nbconvert/__init__.py", line 5, in <module>
    from .exporters import *
  File "/home/sfleming/miniforge3/envs/cellbender/lib/python3.7/site-packages/nbconvert/exporters/__init__.py", line 1, in <module>
    from .asciidoc import ASCIIDocExporter
  File "/home/sfleming/miniforge3/envs/cellbender/lib/python3.7/site-packages/nbconvert/exporters/asciidoc.py", line 9, in <module>
    from .templateexporter import TemplateExporter
  File "/home/sfleming/miniforge3/envs/cellbender/lib/python3.7/site-packages/nbconvert/exporters/templateexporter.py", line 25, in <module>
    from lxml.html.clean import clean_html
  File "/home/sfleming/miniforge3/envs/cellbender/lib/python3.7/site-packages/lxml/html/clean.py", line 21, in <module>
    ) from None
ImportError: lxml.html.clean module is now a separate project lxml_html_clean.
Install lxml[html_clean] or lxml_html_clean directly.

https://www.mail-archive.com/debian-bugs-dist@lists.debian.org/msg1964938.html

Putting lxml_html_clean into requirements.txt resolves the issue.

yesitsjess commented 2 months ago

Sorry, I'm not great with conda/python, is there an easy way to just add lxml[html_clean] to requirements.txt for now until its fixed on github?

sjfleming commented 2 months ago

You have three options I think @yesitsjess :

  1. currently it is fixed on the master branch on github, so if you install from the master branch, it will be included in requirements.txt
  2. if you are installing from source (from github), you can just modify that requirements.txt file yourself like this
  3. you can install cellbender however you like, including pip install cellbender, and then after that you can run pip install lxml_html_clean. That's the easiest way!
schroeme commented 2 weeks ago

I am still having this issue using v0.3.0 installed from pip in a fresh conda environment with python 3.7. I did pip install lxml_html_clean afterwards and got the following error when running cellbender remove-background:

[NbConvertApp] Converting notebook tmp.report.ipynb to notebook
[NbConvertApp] Writing 520710 bytes to tmp.report.nbconvert.ipynb
[NbConvertApp] Converting notebook tmp.report.nbconvert.ipynb to html
Traceback (most recent call last):
  File "/home/mschro/anaconda3/envs/cellbender_new/bin/jupyter-nbconvert", line 8, in <module>
    sys.exit(main())
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/jupyter_core/application.py", line 269, in launch_instance
    return super().launch_instance(argv=argv, **kwargs)
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/traitlets/config/application.py", line 1043, in launch_instance
    app.start()
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/nbconvert/nbconvertapp.py", line 423, in start
    self.convert_notebooks()
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/nbconvert/nbconvertapp.py", line 597, in convert_notebooks
    self.convert_single_notebook(notebook_filename)
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/nbconvert/nbconvertapp.py", line 561, in convert_single_notebook
    notebook_filename, resources, input_buffer=input_buffer
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/nbconvert/nbconvertapp.py", line 489, in export_single_notebook
    notebook_filename, resources=resources
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/nbconvert/exporters/exporter.py", line 189, in from_filename
    return self.from_file(f, resources=resources, **kw)
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/nbconvert/exporters/exporter.py", line 207, in from_file
    nbformat.read(file_stream, as_version=4), resources=resources, **kw
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/nbconvert/exporters/html.py", line 223, in from_notebook_node
    return super().from_notebook_node(nb, resources, **kw)
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/nbconvert/exporters/templateexporter.py", line 413, in from_notebook_node
    output = self.template.render(nb=nb_copy, resources=resources)
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/jinja2/environment.py", line 1304, in render
    self.environment.handle_exception()
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/jinja2/environment.py", line 939, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "/home/mschro/.local/share/jupyter/nbconvert/templates/lab/index.html.j2", line 1, in top-level template code
    {%- extends 'base.html.j2' -%}
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/jinja2/environment.py", line 939, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "/home/mschro/.local/share/jupyter/nbconvert/templates/lab/base.html.j2", line 306, in template
    {{ output.data[datatype] | json_dumps | escape_html_script }}
jinja2.exceptions.TemplateAssertionError: No filter named 'escape_html_script'.
mv: cannot stat 'tmp.report.nbconvert.html': No such file or directory
cellbender:remove-background: Unable to create report.
cellbender:remove-background: Traceback (most recent call last):
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/cellbender/remove_background/run.py", line 351, in compute_output_denoised_counts_reports_metrics
    output=html_report_file,
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/cellbender/remove_background/report.py", line 82, in run_notebook_make_html
    title=('CellBender: ' + os.path.basename(output).replace('_report.html', '')),
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/cellbender/remove_background/report.py", line 59, in _postprocess_html
    with open(file, mode='r') as f:
FileNotFoundError: [Errno 2] No such file or directory: './Exp110_mmE18.5_1A_output_report.html'

Should I just install the dev version?

Thanks! Margaret

carolienv commented 2 weeks ago

I am still having this issue using v0.3.0 installed from pip in a fresh conda environment with python 3.7. I did pip install lxml_html_clean afterwards and got the following error when running cellbender remove-background:

[NbConvertApp] Converting notebook tmp.report.ipynb to notebook
[NbConvertApp] Writing 520710 bytes to tmp.report.nbconvert.ipynb
[NbConvertApp] Converting notebook tmp.report.nbconvert.ipynb to html
Traceback (most recent call last):
  File "/home/mschro/anaconda3/envs/cellbender_new/bin/jupyter-nbconvert", line 8, in <module>
    sys.exit(main())
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/jupyter_core/application.py", line 269, in launch_instance
    return super().launch_instance(argv=argv, **kwargs)
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/traitlets/config/application.py", line 1043, in launch_instance
    app.start()
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/nbconvert/nbconvertapp.py", line 423, in start
    self.convert_notebooks()
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/nbconvert/nbconvertapp.py", line 597, in convert_notebooks
    self.convert_single_notebook(notebook_filename)
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/nbconvert/nbconvertapp.py", line 561, in convert_single_notebook
    notebook_filename, resources, input_buffer=input_buffer
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/nbconvert/nbconvertapp.py", line 489, in export_single_notebook
    notebook_filename, resources=resources
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/nbconvert/exporters/exporter.py", line 189, in from_filename
    return self.from_file(f, resources=resources, **kw)
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/nbconvert/exporters/exporter.py", line 207, in from_file
    nbformat.read(file_stream, as_version=4), resources=resources, **kw
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/nbconvert/exporters/html.py", line 223, in from_notebook_node
    return super().from_notebook_node(nb, resources, **kw)
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/nbconvert/exporters/templateexporter.py", line 413, in from_notebook_node
    output = self.template.render(nb=nb_copy, resources=resources)
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/jinja2/environment.py", line 1304, in render
    self.environment.handle_exception()
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/jinja2/environment.py", line 939, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "/home/mschro/.local/share/jupyter/nbconvert/templates/lab/index.html.j2", line 1, in top-level template code
    {%- extends 'base.html.j2' -%}
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/jinja2/environment.py", line 939, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "/home/mschro/.local/share/jupyter/nbconvert/templates/lab/base.html.j2", line 306, in template
    {{ output.data[datatype] | json_dumps | escape_html_script }}
jinja2.exceptions.TemplateAssertionError: No filter named 'escape_html_script'.
mv: cannot stat 'tmp.report.nbconvert.html': No such file or directory
cellbender:remove-background: Unable to create report.
cellbender:remove-background: Traceback (most recent call last):
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/cellbender/remove_background/run.py", line 351, in compute_output_denoised_counts_reports_metrics
    output=html_report_file,
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/cellbender/remove_background/report.py", line 82, in run_notebook_make_html
    title=('CellBender: ' + os.path.basename(output).replace('_report.html', '')),
  File "/home/mschro/anaconda3/envs/cellbender_new/lib/python3.7/site-packages/cellbender/remove_background/report.py", line 59, in _postprocess_html
    with open(file, mode='r') as f:
FileNotFoundError: [Errno 2] No such file or directory: './Exp110_mmE18.5_1A_output_report.html'

Should I just install the dev version?

Thanks! Margaret

I have the same issue, any updates on how to handle this?

carolienv commented 2 weeks ago

The problem is in the filter names, they are incorrect. I tried downgrading some package versions, but nothing solved it. So eventually I decided to change the base.html.j2 and index.html.j2 templates. This solved the issue for me. You can check the allowed filters via:

from jinja2 import Environment
env = Environment()
available_filters = env.filters
for filter_name in available_filters: print(filter_name)
# base.html.j2
{%- extends 'display_priority.j2' -%}
{% from 'celltags.j2' import celltags %}

{% block codecell %}
{%- if not cell.outputs -%}
{%- set no_output_class="jp-mod-noOutputs" -%}
{%- endif -%}
{%- if not resources.global_content_filter.include_input -%}
{%- set no_input_class="jp-mod-noInput" -%}
{%- endif -%}
<div class="jp-Cell jp-CodeCell jp-Notebook-cell {{ no_output_class }} {{ no_input_class }} {{ celltags(cell) }}">
{{ super() }}
</div>
{%- endblock codecell %}

{% block input_group -%}
<div class="jp-Cell-inputWrapper">
<div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser">
</div>
<div class="jp-InputArea jp-Cell-inputArea">
{{ super() }}
</div>
</div>
{% endblock input_group %}

{% block input %}
<div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline">
     <div class="CodeMirror cm-s-jupyter">
{{ cell.source | highlight_code(metadata=cell.metadata) | safe }}
     </div>
</div>
{%- endblock input %}

{% block output_group %}
<div class="jp-Cell-outputWrapper">
<div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser">
</div>
{{ super() }}
</div>
{% endblock output_group %}

{% block outputs %}
<div class="jp-OutputArea jp-Cell-outputArea">
{{ super() }}
</div>
{% endblock outputs %}

{% block in_prompt -%}
<div class="jp-InputPrompt jp-InputArea-prompt">
    {%- if cell.execution_count is defined -%}
        In&nbsp;[{{ cell.execution_count|default("&nbsp;") }}]:
    {%- else -%}
        In&nbsp;[&nbsp;]:
    {%- endif -%}
</div>
{%- endblock in_prompt %}

{% block empty_in_prompt -%}
<div class="jp-InputPrompt jp-InputArea-prompt">
</div>
{%- endblock empty_in_prompt %}

{% block output_prompt %}
{% endblock output_prompt %}

{% block output_area_prompt %}
    <div class="jp-OutputPrompt jp-OutputArea-prompt">
    {%- if output.output_type == 'execute_result' -%}
        {%- if cell.execution_count is defined -%}
            Out[{{ cell.execution_count|default("&nbsp;") }}]:
        {%- else -%}
            Out[&nbsp;]:
        {%- endif -%}
    {%- endif -%}
    </div>
{% endblock output_area_prompt %}

{% block output %}
<div class="jp-OutputArea-child">
{% if resources.global_content_filter.include_output_prompt %}
    {{ self.output_area_prompt() }}
{% endif %}
{{ super() }}
</div>
{% endblock output %}

{% block markdowncell scoped %}
<div class="jp-Cell jp-MarkdownCell jp-Notebook-cell">
<div class="jp-Cell-inputWrapper">
<div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser">
</div>
<div class="jp-InputArea jp-Cell-inputArea">
{%- if resources.global_content_filter.include_input_prompt-%}
    {{ self.empty_in_prompt() }}
{%- endif -%}
<div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput {{ celltags(cell) }}" data-mime-type="text/markdown">
{%- if resources.should_sanitize_html %}
{%- set html_value=cell.source  | markdown2html | strip_files_prefix | safe %}
{%- else %}
{%- set html_value=cell.source  | markdown2html | strip_files_prefix %}
{%- endif %}
{{ html_value }}
</div>
</div>
</div>
</div>
{%- endblock markdowncell %}

{% block rawcell scoped %}
{%- if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) -%}
{{ cell.source | safe }}
{%- endif -%}
{%- endblock rawcell %}

{% block unknowncell scoped %}
unknown type  {{ cell.type }}
{% endblock unknowncell %}

{% block execute_result -%}
{%- set extra_class="jp-OutputArea-executeResult" -%}
{% block data_priority scoped %}
{{ super() }}
{% endblock data_priority %}
{%- set extra_class="" -%}
{%- endblock execute_result %}

{% block stream_stdout -%}
<div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain">
<pre>
{{- output.text | ansi2html -}}
</pre>
</div>
{%- endblock stream_stdout %}

{% block stream_stderr -%}
<div class="jp-RenderedText jp-OutputArea-output" data-mime-type="application/vnd.jupyter.stderr">
<pre>
{{- output.text | ansi2html -}}
</pre>
</div>
{%- endblock stream_stderr %}

{% block stream_stdin -%}
{%- if resources.global_content_filter.include_output_stdin -%}
<div class="jp-RenderedText jp-OutputArea-output" data-mime-type="application/vnd.jupyter.stdin">
<pre>
{{- output.text | ansi2html -}}
</pre>
</div>
{%- endif %}
{%- endblock stream_stdin %}

{% block data_svg scoped -%}
<div class="jp-RenderedSVG jp-OutputArea-output {{ extra_class }}" data-mime-type="image/svg+xml">
{%- if output.svg_filename %}
<img src="{{ output.svg_filename | posix_path | escape }}">
{%- else %}
{{ output.data['image/svg+xml'] | safe }}
{%- endif %}
</div>
{%- endblock data_svg %}

{% block data_html scoped -%}
<div class="jp-RenderedHTMLCommon jp-RenderedHTML jp-OutputArea-output {{ extra_class }}" data-mime-type="text/html">
{%- if resources.should_sanitize_html %}
{{ output.data['text/html'] | safe }}
{%- else %}
{{ output.data['text/html'] }}
{%- endif %}
</div>
{%- endblock data_html %}

{% block data_markdown scoped -%}
{%- if resources.should_sanitize_html %}
{%- set html_value=output.data['text/markdown'] | markdown2html | safe %}
{%- else %}
{%- set html_value=output.data['text/markdown'] | markdown2html %}
{%- endif %}
<div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-OutputArea-output {{ extra_class }}" data-mime-type="text/markdown">
{{ html_value }}
</div>
{%- endblock data_markdown %}

{% block data_png scoped %}
<div class="jp-RenderedImage jp-OutputArea-output {{ extra_class }}">
{%- if 'image/png' in output.metadata.get('filenames', {}) %}
<img src="{{ output.metadata.filenames['image/png'] | posix_path | escape }}">
{%- else %}
<img src="data:image/png;base64,{{ output.data['image/png'] | escape }}">
{%- endif %}
{%- set width=output | get_metadata('width', 'image/png') -%}
{%- if width is not none %}
width={{ width | escape }}
{%- endif %}
{%- set height=output | get_metadata('height', 'image/png') -%}
{%- if height is not none %}
height={{ height | escape }}
{%- endif %}
class="
{%- if output | get_metadata('unconfined', 'image/png') %}
unconfined
{%- endif %}
{%- if output | get_metadata('needs_background', 'image/png') == 'light' %}
jp-needs-light-background
{%- endif %}
{%- if output | get_metadata('needs_background', 'image/png') == 'dark' %}
jp-needs-dark-background
{%- endif %}
"
>
</div>
{%- endblock data_png %}

{% block data_jpg scoped %}
<div class="jp-RenderedImage jp-OutputArea-output {{ extra_class }}">
{%- if 'image/jpeg' in output.metadata.get('filenames', {}) %}
<img src="{{ output.metadata.filenames['image/jpeg'] | posix_path | escape }}">
{%- else %}
<img src="data:image/jpeg;base64,{{ output.data['image/jpeg'] | escape }}">
{%- endif %}
{%- set width=output | get_metadata('width', 'image/jpeg') -%}
{%- if width is not none %}
width={{ width | escape }}
{%- endif %}
{%- set height=output | get_metadata('height', 'image/jpeg') -%}
{%- if height is not none %}
height={{ height | escape }}
{%- endif %}
class="
{%- if output | get_metadata('unconfined', 'image/jpeg') %}
unconfined
{%- endif %}
{%- if output | get_metadata('needs_background', 'image/jpeg') == 'light' %}
jp-needs-light-background
{%- endif %}
{%- if output | get_metadata('needs_background', 'image/jpeg') == 'dark' %}
jp-needs-dark-background
{%- endif %}
"
>
</div>
{%- endblock data_jpg %}

{% block data_latex scoped %}
<div class="jp-RenderedLatex jp-OutputArea-output {{ extra_class }}" data-mime-type="text/latex">
{{ output.data['text/latex'] | e }}
</div>
{%- endblock data_latex %}

{% block error -%}
<div class="jp-RenderedText jp-OutputArea-output" data-mime-type="application/vnd.jupyter.stderr">
<pre>
{{- super() -}}
</pre>
</div>
{%- endblock error %}

{%- block traceback_line %}
{{ line | ansi2html }}
{%- endblock traceback_line %}

{%- block data_text scoped %}
<div class="jp-RenderedText jp-OutputArea-output {{ extra_class }}" data-mime-type="text/plain">
<pre>
{{- output.data['text/plain'] | ansi2html -}}
</pre>
</div>
{%- endblock -%}

{%- block data_javascript scoped %}
{% set div_id = uuid4() %}
<div id="{{ div_id }}" class="jp-RenderedJavaScript jp-OutputArea-output {{ extra_class }}" data-mime-type="application/javascript">
{%- if not resources.should_sanitize_html %}
<script type="text/javascript">
var element = document.getElementById('{{ div_id }}');
{{ output.data['application/javascript'] }}
</script>
{%- endif %}
</div>
{%- endblock -%}

{%- block data_widget_view scoped %}
{% set div_id = uuid4() %}
{% set datatype_list = output.data | filter_data_type %}
{% set datatype = datatype_list[0] %}
<div id="{{ div_id }}" class="jupyter-widgets jp-OutputArea-output {{ extra_class }}">
<script type="text/javascript">
var element = document.getElementById('{{ div_id }}');
</script>
<script type="{{ datatype }}">
{{ output.data[datatype] | json_dumps | safe }}
</script>
</div>
{%- endblock data_widget_view -%}

{%- block footer %}
{% set mimetype = 'application/vnd.jupyter.widget-state+json' %}
{% if mimetype in nb.metadata.get("widgets", {}) %}
<script type="{{ mimetype }}">
{{ nb.metadata.widgets[mimetype] | json_dumps | safe }}
</script>
{% endif %}
{{ super() }}
{%- endblock footer-%}
# index.html.j2
{%- extends 'base.html.j2' -%}
{% from 'mathjax.html.j2' import mathjax %}
{% from 'jupyter_widgets.html.j2' import jupyter_widgets %}

{%- block header -%}
<!DOCTYPE html>
<html>
<head>
{%- block html_head -%}
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% set nb_title = nb.metadata.get('title', resources['metadata']['name']) | safe %}
<title>{{ nb_title }}</title>

{%- block html_head_js -%}
{%- block html_head_js_requirejs -%}
<script src="{{ resources.require_js_url | safe }}"></script>
{%- endblock html_head_js_requirejs -%}
{%- endblock html_head_js -%}

{% block jupyter_widgets %}
  {%- if "widgets" in nb.metadata -%}
    {{ jupyter_widgets(resources.jupyter_widgets_base_url | safe, resources.html_manager_semver_range | safe, resources.widget_renderer_url | safe) | safe }}
  {%- endif -%}
{% endblock jupyter_widgets %}

{% block extra_css %}
{% endblock extra_css %}

{% for css in resources.inlining.css -%}
  <style type="text/css">
    {{ css | safe }}
  </style>
{% endfor %}

{% block notebook_css %}
{{ resources.include_css("static/index.css") | safe }}
{% if resources.theme == 'dark' %}
    {{ resources.include_css("static/theme-dark.css") | safe }}
{% elif resources.theme == 'light'  %}
    {{ resources.include_css("static/theme-light.css") | safe }}
{% else %}
    {{ resources.include_lab_theme(resources.theme) | safe }}
{% endif %}
<style type="text/css">
/* Force rendering true colors when outputing to pdf */
* {
  -webkit-print-color-adjust: exact;
}

/* Misc */
a.anchor-link {
  display: none;
}

.highlight  {
  margin: 0.4em;
}

/* Input area styling */
.jp-InputArea {
  overflow: hidden;
}

.jp-InputArea-editor {
  overflow: hidden;
}

.CodeMirror pre {
  margin: 0;
  padding: 0;
}

/* Using table instead of flexbox so that we can use break-inside property */
/* CSS rules under this comment should not be required anymore after we move to the JupyterLab 4.0 CSS */

.jp-CodeCell.jp-mod-outputsScrolled .jp-OutputArea-prompt {
  min-width: calc(
    var(--jp-cell-prompt-width) - var(--jp-private-cell-scrolling-output-offset)
  );
}

.jp-OutputArea-child {
  display: table;
  width: 100%;
}

.jp-OutputPrompt {
  display: table-cell;
  vertical-align: top;
  min-width: var(--jp-cell-prompt-width);
}

body[data-format='mobile'] .jp-OutputPrompt {
  display: table-row;
}

.jp-OutputArea-output {
  display: table-cell;
  width: 100%;
}

body[data-format='mobile'] .jp-OutputArea-child .jp-OutputArea-output {
  display: table-row;
}

.jp-OutputArea-output.jp-OutputArea-executeResult {
  width: 100%;
}

/* Hiding the collapser by default */
.jp-Collapser {
  display: none;
}

@media print {
  .jp-Cell-inputWrapper,
  .jp-Cell-outputWrapper {
    display: block;
  }

  .jp-OutputArea-child {
    break-inside: avoid-page;
  }
}
</style>

{% endblock notebook_css %}

{%- block html_head_js_mathjax -%}
{{ mathjax(resources.mathjax_url | safe) | safe }}
{%- endblock html_head_js_mathjax -%}

{%- block html_head_css -%}
{%- endblock html_head_css -%}

{%- endblock html_head -%}
</head>
{%- endblock header -%}

{%- block body_header -%}
{% if resources.theme == 'dark' %}
<body class="jp-Notebook" data-jp-theme-light="false" data-jp-theme-name="JupyterLab Dark">
{% else %}
<body class="jp-Notebook" data-jp-theme-light="true" data-jp-theme-name="JupyterLab Light">
{% endif %}
{%- endblock body_header -%}

{% block body_footer %}
</body>
{% endblock body_footer %}

{% block footer %}
{% block footer_js %}
{% endblock footer_js %}
{{ super() | safe }}
</html>
{% endblock footer %}