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

Add type checking #1930

Merged
merged 5 commits into from Dec 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Expand Up @@ -67,6 +67,7 @@ jobs:
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
- name: Run Linters
run: |
hatch run typing:test
hatch run lint:style
pipx run interrogate -v .
pipx run doc8 --max-line-length=200 --ignore-path=docs/source/other/full-config.rst
Expand Down
2 changes: 1 addition & 1 deletion nbconvert/conftest.py
Expand Up @@ -3,4 +3,4 @@
import os

if os.name == "nt":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) # type:ignore
18 changes: 12 additions & 6 deletions nbconvert/exporters/exporter.py
Expand Up @@ -11,10 +11,10 @@
import datetime
import os
import sys
from typing import Optional
import typing as t

import nbformat
from nbformat import validator
from nbformat import NotebookNode, validator
from traitlets import Bool, HasTraits, List, TraitError, Unicode
from traitlets.config import Config
from traitlets.config.configurable import LoggingConfigurable
Expand Down Expand Up @@ -75,7 +75,7 @@ class Exporter(LoggingConfigurable):

# Should this converter be accessible from the notebook front-end?
# If so, should be a friendly name to display (and possibly translated).
export_from_notebook = None
export_from_notebook: str = None # type:ignore

# Configurability, allows the user to easily add filters and preprocessors.
preprocessors = List(help="""List of preprocessors, by name or namespace, to enable.""").tag(
Expand Down Expand Up @@ -126,7 +126,9 @@ def __init__(self, config=None, **kw):
def default_config(self):
return Config()

def from_notebook_node(self, nb, resources=None, **kw):
def from_notebook_node(
self, nb: NotebookNode, resources: t.Optional[t.Any] = None, **kw: t.Any
) -> t.Tuple[NotebookNode, t.Dict]:
"""
Convert a notebook from a notebook node instance.

Expand Down Expand Up @@ -157,7 +159,9 @@ def from_notebook_node(self, nb, resources=None, **kw):
self._nb_metadata[notebook_name] = nb_copy.metadata
return nb_copy, resources

def from_filename(self, filename: str, resources: Optional[dict] = None, **kw):
def from_filename(
self, filename: str, resources: t.Optional[dict] = None, **kw: t.Any
) -> t.Tuple[NotebookNode, t.Dict]:
"""
Convert a notebook from a notebook file.

Expand Down Expand Up @@ -193,7 +197,9 @@ def from_filename(self, filename: str, resources: Optional[dict] = None, **kw):
with open(filename, encoding="utf-8") as f:
return self.from_file(f, resources=resources, **kw)

def from_file(self, file_stream, resources=None, **kw):
def from_file(
self, file_stream: t.Any, resources: t.Optional[dict] = None, **kw: t.Any
) -> t.Tuple[NotebookNode, dict]:
"""
Convert a notebook from a notebook file.

Expand Down
14 changes: 8 additions & 6 deletions nbconvert/exporters/html.py
Expand Up @@ -8,6 +8,7 @@
import mimetypes
import os
from pathlib import Path
from typing import Any, Dict, Optional, Tuple

import jinja2
import markupsafe
Expand All @@ -16,11 +17,12 @@
from traitlets.config import Config

if tuple(int(x) for x in jinja2.__version__.split(".")[:3]) < (3, 0, 0):
from jinja2 import contextfilter
from jinja2 import contextfilter # type:ignore
else:
from jinja2 import pass_context as contextfilter

from jinja2.loaders import split_template_path
from nbformat import NotebookNode

from nbconvert.filters.highlight import Highlight2HTML
from nbconvert.filters.markdown_mistune import IPythonRenderer, MarkdownWithMath
Expand Down Expand Up @@ -208,7 +210,9 @@ def default_filters(self):
yield from super().default_filters()
yield ("markdown2html", self.markdown2html)

def from_notebook_node(self, nb, resources=None, **kw):
def from_notebook_node( # type:ignore
self, nb: NotebookNode, resources: Optional[Dict] = None, **kw: Any
) -> Tuple[str, Dict]:
"""Convert from notebook node."""
langinfo = nb.metadata.get("language_info", {})
lexer = langinfo.get("pygments_lexer", langinfo.get("name", None))
Expand Down Expand Up @@ -247,11 +251,9 @@ def resources_include_lab_theme(name):
# Replace asset url by a base64 dataurl
with open(theme_path / asset, "rb") as assetfile:
base64_data = base64.b64encode(assetfile.read())
base64_data = base64_data.replace(b"\n", b"").decode("ascii")
base64_str = base64_data.replace(b"\n", b"").decode("ascii")

data = data.replace(
local_url, f"url(data:{mime_type};base64,{base64_data})"
)
data = data.replace(local_url, f"url(data:{mime_type};base64,{base64_str})")

code = """<style type="text/css">\n%s</style>""" % data
return markupsafe.Markup(code)
Expand Down
14 changes: 5 additions & 9 deletions nbconvert/exporters/pdf.py
Expand Up @@ -122,7 +122,7 @@ def run_command(self, command_list, filename, count, log_function, raise_on_fail

shell = sys.platform == "win32"
if shell:
command = subprocess.list2cmdline(command)
command = subprocess.list2cmdline(command) # type:ignore
env = os.environ.copy()
prepend_to_env_search_path("TEXINPUTS", self.texinputs, env)
prepend_to_env_search_path("BIBINPUTS", self.texinputs, env)
Expand All @@ -144,17 +144,13 @@ def run_command(self, command_list, filename, count, log_function, raise_on_fail
if self.verbose:
# verbose means I didn't capture stdout with PIPE,
# so it's already been displayed and `out` is None.
out = ""
out_str = ""
else:
out = out.decode("utf-8", "replace")
out_str = out.decode("utf-8", "replace")
log_function(command, out)
self._captured_output.append(out)
self._captured_output.append(out_str)
if raise_on_failure:
raise raise_on_failure(
'Failed to run "{command}" command:\n{output}'.format(
command=command, output=out
)
)
raise raise_on_failure(f'Failed to run "{command}" command:\n{out_str}')
return False # failure
return True # success

Expand Down
1 change: 1 addition & 0 deletions nbconvert/exporters/qt_exporter.py
Expand Up @@ -12,6 +12,7 @@ class QtExporter(HTMLExporter):
"""A qt exporter."""

paginate = None
format = ""

@default("file_extension")
def _file_extension_default(self):
Expand Down
14 changes: 7 additions & 7 deletions nbconvert/exporters/qt_screenshot.py
Expand Up @@ -2,10 +2,10 @@
import os

try:
from PyQt5 import QtCore
from PyQt5.QtGui import QPageLayout, QPageSize
from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineView
from PyQt5.QtWidgets import QApplication
from PyQt5 import QtCore # type:ignore
from PyQt5.QtGui import QPageLayout, QPageSize # type:ignore
from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineView # type:ignore
from PyQt5.QtWidgets import QApplication # type:ignore

QT_INSTALLED = True
except ModuleNotFoundError:
Expand Down Expand Up @@ -40,7 +40,7 @@ def capture(self, url, output_file, paginate):

def cleanup(*args):
"""Cleanup the app."""
self.app.quit()
self.app.quit() # type:ignore
self.get_data()

self.page().pdfPrintingFinished.connect(cleanup)
Expand All @@ -49,7 +49,7 @@ def cleanup(*args):
else:
raise RuntimeError(f"Export file extension not supported: {output_file}")
self.show()
self.app.exec()
self.app.exec() # type:ignore

def on_loaded(self):
"""Handle app load."""
Expand All @@ -76,7 +76,7 @@ def export_pdf(self):
def export_png(self):
"""Export to png."""
self.grab().save(self.output_file, "PNG")
self.app.quit()
self.app.quit() # type:ignore
self.get_data()

def get_data(self):
Expand Down
28 changes: 22 additions & 6 deletions nbconvert/exporters/templateexporter.py
Expand Up @@ -9,6 +9,7 @@
import html
import json
import os
import typing as t
import uuid
import warnings
from pathlib import Path
Expand All @@ -22,6 +23,7 @@
TemplateNotFound,
)
from jupyter_core.paths import jupyter_path
from nbformat import NotebookNode
from traitlets import Bool, Dict, HasTraits, List, Unicode, default, observe, validate
from traitlets.config import Config
from traitlets.utils.importstring import import_item
Expand Down Expand Up @@ -221,7 +223,7 @@ def _template_name_validate(self, change):
def _template_file_changed(self, change):
new = change["new"]
if new == "default":
self.template_file = self.default_template
self.template_file = self.default_template # type:ignore
return
# check if template_file is a file path
# rather than a name already on template_path
Expand Down Expand Up @@ -374,7 +376,21 @@ def _load_template(self):
self.log.debug(" template_paths: %s", os.pathsep.join(self.template_paths))
return self.environment.get_template(template_file)

def from_notebook_node(self, nb, resources=None, **kw):
def from_filename( # type:ignore
self, filename: str, resources: t.Optional[dict] = None, **kw: t.Any
) -> t.Tuple[str, dict]:
"""Convert a notebook from a filename."""
return super().from_filename(filename, resources, **kw) # type:ignore

def from_file( # type:ignore
self, file_stream: t.Any, resources: t.Optional[dict] = None, **kw: t.Any
) -> t.Tuple[str, dict]:
"""Convert a notebook from a file."""
return super().from_file(file_stream, resources, **kw) # type:ignore

def from_notebook_node( # type:ignore
self, nb: NotebookNode, resources: t.Optional[dict] = None, **kw: t.Any
) -> t.Tuple[str, dict]:
"""
Convert a notebook from a notebook node instance.

Expand Down Expand Up @@ -515,7 +531,7 @@ def _init_preprocessors(self):
# * We rely on recursive_update, which can only merge dicts, lists will be overwritten
# * We can use the key with numerical prefixing to guarantee ordering (/etc/*.d/XY-file style)
# * We can disable preprocessors by overwriting the value with None
for _, preprocessor in sorted(preprocessors.items(), key=lambda x: x[0]):
for _, preprocessor in sorted(preprocessors.items(), key=lambda x: x[0]): # type:ignore
if preprocessor is not None:
kwargs = preprocessor.copy()
preprocessor_cls = kwargs.pop("type")
Expand All @@ -526,7 +542,7 @@ def _init_preprocessors(self):
self.register_preprocessor(preprocessor)

def _get_conf(self):
conf = {} # the configuration once all conf files are merged
conf: dict = {} # the configuration once all conf files are merged
for path in map(Path, self.template_paths):
conf_path = path / "conf.json"
if conf_path.exists():
Expand Down Expand Up @@ -583,10 +599,10 @@ def get_template_names(self):
template_names = []
root_dirs = self.get_prefix_root_dirs()
base_template = self.template_name
merged_conf = {} # the configuration once all conf files are merged
merged_conf: dict = {} # the configuration once all conf files are merged
while base_template is not None:
template_names.append(base_template)
conf = {}
conf: dict = {}
found_at_least_one = False
for base_dir in self.extra_template_basedirs:
template_dir = os.path.join(base_dir, base_template)
Expand Down
2 changes: 1 addition & 1 deletion nbconvert/exporters/tests/test_asciidoc.py
Expand Up @@ -30,7 +30,7 @@
class TestASCIIDocExporter(ExportersTestsBase):
"""Tests for ASCIIDocExporter"""

exporter_class = ASCIIDocExporter
exporter_class = ASCIIDocExporter # type:ignore

def test_constructor(self):
"""
Expand Down
6 changes: 4 additions & 2 deletions nbconvert/exporters/tests/test_html.py
Expand Up @@ -15,8 +15,8 @@
class TestHTMLExporter(ExportersTestsBase):
"""Tests for HTMLExporter"""

exporter_class = HTMLExporter
should_include_raw = ["html"]
exporter_class = HTMLExporter # type:ignore
should_include_raw = ["html"] # type:ignore

def test_constructor(self):
"""
Expand Down Expand Up @@ -78,6 +78,7 @@ def test_png_metadata(self):
)
check_for_png = re.compile(r'<img src="[^"]*?"([^>]*?)>')
result = check_for_png.search(output)
assert result
attr_string = result.group(1)
assert "width" in attr_string
assert "height" in attr_string
Expand All @@ -104,6 +105,7 @@ def test_attachments(self):
)
check_for_png = re.compile(r'<img src="[^"]*?"([^>]*?)>')
result = check_for_png.search(output)
assert result
self.assertTrue(result.group(0).strip().startswith('<img src="data:image/png;base64,iVBOR'))
self.assertTrue(result.group(1).strip().startswith('alt="image.png"'))

Expand Down
4 changes: 2 additions & 2 deletions nbconvert/exporters/tests/test_latex.py
Expand Up @@ -22,8 +22,8 @@
class TestLatexExporter(ExportersTestsBase):
"""Contains test functions for latex.py"""

exporter_class = LatexExporter
should_include_raw = ["latex"]
exporter_class = LatexExporter # type:ignore
should_include_raw = ["latex"] # type:ignore

def test_constructor(self):
"""
Expand Down
4 changes: 2 additions & 2 deletions nbconvert/exporters/tests/test_markdown.py
Expand Up @@ -23,8 +23,8 @@
class TestMarkdownExporter(ExportersTestsBase):
"""Tests for MarkdownExporter"""

exporter_class = MarkdownExporter
should_include_raw = ["markdown", "html"]
exporter_class = MarkdownExporter # type:ignore
should_include_raw = ["markdown", "html"] # type:ignore

def test_constructor(self):
"""
Expand Down
10 changes: 6 additions & 4 deletions nbconvert/exporters/tests/test_notebook.py
Expand Up @@ -16,26 +16,28 @@
class TestNotebookExporter(ExportersTestsBase):
"""Contains test functions for notebook.py"""

exporter_class = NotebookExporter
exporter_class = NotebookExporter # type:ignore

def test_export(self):
"""
Does the NotebookExporter return the file unchanged?
"""
with open(self._get_notebook()) as f:
file_contents = f.read()
(output, resources) = self.exporter_class().from_filename(self._get_notebook())
(output, resources) = self.exporter_class().from_filename( # type:ignore
self._get_notebook()
)
assert len(output) > 0
assert_big_text_equal(output, file_contents)

def test_downgrade_3(self):
exporter = self.exporter_class(nbformat_version=3)
exporter = self.exporter_class(nbformat_version=3) # type:ignore
(output, resources) = exporter.from_filename(self._get_notebook())
nb = json.loads(output)
validate(nb)

def test_downgrade_2(self):
exporter = self.exporter_class(nbformat_version=2)
exporter = self.exporter_class(nbformat_version=2) # type:ignore
(output, resources) = exporter.from_filename(self._get_notebook())
nb = json.loads(output)
self.assertEqual(nb["nbformat"], 2)
8 changes: 5 additions & 3 deletions nbconvert/exporters/tests/test_pdf.py
Expand Up @@ -19,11 +19,11 @@
class TestPDF(ExportersTestsBase):
"""Test PDF export"""

exporter_class = PDFExporter
exporter_class = PDFExporter # type:ignore

def test_constructor(self):
"""Can a PDFExporter be constructed?"""
self.exporter_class()
self.exporter_class() # type:ignore

@onlyif_cmds_exist("xelatex", "pandoc")
def test_export(self):
Expand All @@ -32,7 +32,9 @@ def test_export(self):
file_name = os.path.basename(self._get_notebook())
newpath = os.path.join(td, file_name)
shutil.copy(self._get_notebook(), newpath)
(output, resources) = self.exporter_class(latex_count=1).from_filename(newpath)
(output, resources) = self.exporter_class(latex_count=1).from_filename( # type:ignore
newpath
)
self.assertIsInstance(output, bytes)
assert len(output) > 0
# all temporary file should be cleaned up
Expand Down