Skip to content

Commit

Permalink
Add type checking (#1930)
Browse files Browse the repository at this point in the history
  • Loading branch information
blink1073 authored Dec 23, 2022
1 parent 201cd58 commit 5baf141
Show file tree
Hide file tree
Showing 43 changed files with 178 additions and 118 deletions.
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit 5baf141

Please sign in to comment.