Skip to content

Commit

Permalink
Merge pull request #88 from MarcoGorelli/type-checking
Browse files Browse the repository at this point in the history
CI Type checking
  • Loading branch information
MarcoGorelli committed Jul 22, 2020
2 parents e4b717c + 1555753 commit f16c7b4
Show file tree
Hide file tree
Showing 21 changed files with 146 additions and 39 deletions.
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ repos:
rev: 5.0.9
hooks:
- id: isort
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.782
hooks:
- id: mypy
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ nbQA
.. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white
:target: https://github.com/pre-commit/pre-commit

.. image:: http://www.mypy-lang.org/static/mypy_badge.svg
:target: http://mypy-lang.org/

Adapter to run any code-quality tool on a Jupyter notebook. Documentation is hosted here_.

Prerequisites
Expand Down
1 change: 1 addition & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ stages:
black . --check
isort . --check-only
flake8
mypy .
displayName: 'static analysis'
- script: |
Expand Down
16 changes: 16 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[mypy]
disallow_untyped_defs = True

# 3rd party libraries
[mypy-pytest]
ignore_missing_imports = True
[mypy-_pytest.capture]
ignore_missing_imports = True
[mypy-setuptools]
ignore_missing_imports = True

# non-nbqa files
[mypy-setup]
ignore_errors = True
[mypy-conf]
ignore_errors = True
52 changes: 34 additions & 18 deletions nbqa/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import sys
import tempfile
from pathlib import Path
from typing import List
from typing import Dict, Iterator, List, Optional, Tuple

from nbqa import (
__version__,
Expand All @@ -17,7 +17,7 @@
)


def _parse_args(raw_args):
def _parse_args(raw_args: Optional[List[str]]) -> Tuple[str, str, List[str]]:
"""
Parse command-line arguments.
"""
Expand Down Expand Up @@ -46,7 +46,7 @@ def _parse_args(raw_args):
return command, root_dir, kwargs


def _get_notebooks(root_dir) -> List[Path]:
def _get_notebooks(root_dir: str) -> Iterator[Path]:
"""
Get generator with all notebooks in directory.
"""
Expand All @@ -55,7 +55,7 @@ def _get_notebooks(root_dir) -> List[Path]:
return (i for i in Path(".").rglob("*.ipynb") if ".ipynb_checkpoints" not in str(i))


def _temp_python_file_for_notebook(notebook, tmpdir):
def _temp_python_file_for_notebook(notebook: Path, tmpdir: str) -> Path:
"""
Get temporary file to save converted notebook into.
"""
Expand All @@ -70,7 +70,9 @@ def _temp_python_file_for_notebook(notebook, tmpdir):
return temp_python_file


def _replace_full_path_out_err(out, err, temp_python_file, notebook):
def _replace_full_path_out_err(
out: str, err: str, temp_python_file: Path, notebook: Path
) -> Tuple[str, str]:
"""
Take care of case when out/err display full path.
"""
Expand All @@ -85,7 +87,9 @@ def _replace_full_path_out_err(out, err, temp_python_file, notebook):
return out, err


def _replace_relative_path_out_err(out, err, notebook):
def _replace_relative_path_out_err(
out: str, err: str, notebook: Path
) -> Tuple[str, str]:
"""
Take care of case when out/err display relative path.
Expand All @@ -109,7 +113,9 @@ def _replace_relative_path_out_err(out, err, notebook):
return out, err


def _map_python_line_to_nb_lines(out, err, temp_python_file, notebook):
def _map_python_line_to_nb_lines(
out: str, err: str, temp_python_file: Path, notebook: Path
) -> Tuple[str, str]:
"""
Make sure stdout and stderr make reference to Jupyter Notebook lines.
"""
Expand All @@ -123,6 +129,7 @@ def _map_python_line_to_nb_lines(out, err, temp_python_file, notebook):
cell_no += 1
cell_count = 0
else:
assert cell_count is not None
cell_count += 1
mapping[n + 1] = f"cell_{cell_no}:{cell_count}"
out = re.sub(
Expand All @@ -135,8 +142,8 @@ def _map_python_line_to_nb_lines(out, err, temp_python_file, notebook):


def _replace_temp_python_file_references_in_out_err(
temp_python_file, notebook, out, err
):
temp_python_file: Path, notebook: Path, out: str, err: str
) -> Tuple[str, str]:
"""
Replace references to temporary directory name with current working directory.
"""
Expand All @@ -146,17 +153,18 @@ def _replace_temp_python_file_references_in_out_err(
return out, err


def _replace_tmpdir_references(out, err, tmpdirname, cwd=None):
def _replace_tmpdir_references(
out: str, err: str, cwd: Optional[Path] = None
) -> Tuple[str, str]:
"""
Replace references to temporary directory name with current working directory.
Examples
--------
>>> out = f"rootdir: {os.path.join('tmp', 'tmpdir')}\\n"
>>> err = ""
>>> tmpdirname = os.path.join('tmp', 'tmpdir')
>>> cwd = Path("nbQA-dev")
>>> out, err = _replace_tmpdir_references(out, err, tmpdirname, cwd)
>>> out, err = _replace_tmpdir_references(out, err, cwd)
>>> out.strip(os.linesep)
'rootdir: nbQA-dev'
"""
Expand All @@ -181,7 +189,7 @@ def _replace_tmpdir_references(out, err, tmpdirname, cwd=None):
return new_out, new_err


def _create_blank_init_files(notebook, tmpdirname):
def _create_blank_init_files(notebook: Path, tmpdirname: str) -> None:
"""
Replicate local (possibly blank) __init__ files to temporary directory.
"""
Expand All @@ -192,7 +200,7 @@ def _create_blank_init_files(notebook, tmpdirname):
Path(tmpdirname).joinpath(i).touch()


def _ensure_cell_separators_remain(temp_python_file):
def _ensure_cell_separators_remain(temp_python_file: Path) -> None:
"""
Isort removes a blank line which separates the cells.
"""
Expand All @@ -203,7 +211,9 @@ def _ensure_cell_separators_remain(temp_python_file):
handle.write(py_file)


def _get_arg(root_dir, tmpdirname, nb_to_py_mapping):
def _get_arg(
root_dir: str, tmpdirname: str, nb_to_py_mapping: Dict[Path, Path]
) -> Path:
"""
Get argument to run command against.
Expand All @@ -230,7 +240,13 @@ def _get_arg(root_dir, tmpdirname, nb_to_py_mapping):
return arg


def _run_command(command, root_dir, tmpdirname, nb_to_py_mapping, kwargs):
def _run_command(
command: str,
root_dir: str,
tmpdirname: str,
nb_to_py_mapping: Dict[Path, Path],
kwargs: List[str],
) -> Tuple[str, str, int]:
"""
Run third-party tool against given file or directory.
"""
Expand Down Expand Up @@ -259,7 +275,7 @@ def _run_command(command, root_dir, tmpdirname, nb_to_py_mapping, kwargs):
return out, err, output_code


def main(raw_args=None):
def main(raw_args: Optional[List[str]] = None) -> None:

command, root_dir, kwargs = _parse_args(raw_args)

Expand Down Expand Up @@ -289,7 +305,7 @@ def main(raw_args=None):
command, root_dir, tmpdirname, nb_to_py_mapping, kwargs
)

out, err = _replace_tmpdir_references(out, err, tmpdirname)
out, err = _replace_tmpdir_references(out, err)

for notebook, temp_python_file in nb_to_py_mapping.items():
out, err = _replace_temp_python_file_references_in_out_err(
Expand Down
6 changes: 5 additions & 1 deletion nbqa/put_magics_back_in.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import re
from typing import TYPE_CHECKING

if TYPE_CHECKING: # pragma: nocover
from pathlib import Path

def main(path):

def main(path: "Path") -> None:

with open(str(path), "r") as handle:
file = handle.read()
Expand Down
6 changes: 5 additions & 1 deletion nbqa/replace_magics.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import re
from typing import TYPE_CHECKING

if TYPE_CHECKING: # pragma: nocover
from pathlib import Path

def main(path):

def main(path: "Path") -> None:

with open(str(path), "r") as handle:
file = handle.read()
Expand Down
8 changes: 6 additions & 2 deletions nbqa/replace_source.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import json
from typing import TYPE_CHECKING

if TYPE_CHECKING: # pragma: nocover
from pathlib import Path

def main(python_file, notebook):

def main(python_file: "Path", notebook: "Path") -> None:
"""
Replace `source` of original notebook.
Replace `source` code cells of original notebook.
"""
with open(notebook, "r") as handle:
notebook_json = json.load(handle)
Expand Down
6 changes: 5 additions & 1 deletion nbqa/save_source.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import json
from typing import TYPE_CHECKING

if TYPE_CHECKING: # pragma: nocover
from pathlib import Path

CODE_SEPARATOR = "\n\n# %%\n"


def main(path, temp_file):
def main(path: "Path", temp_file: "Path") -> None:

with open(path, "r") as handle:
notebook = json.load(handle)
Expand Down
8 changes: 6 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import shutil
from pathlib import Path
from typing import TYPE_CHECKING, Iterator

import pytest

if TYPE_CHECKING:
from py._path.local import LocalPath


@pytest.fixture
def tmp_notebook_for_testing(tmpdir):
def tmp_notebook_for_testing(tmpdir: "LocalPath") -> Iterator[Path]:
"""
Make temporary copy of test notebook before it's operated on, then revert it.
"""
Expand All @@ -21,7 +25,7 @@ def tmp_notebook_for_testing(tmpdir):


@pytest.fixture
def tmp_notebook_starting_with_md(tmpdir):
def tmp_notebook_starting_with_md(tmpdir: "LocalPath") -> Iterator[Path]:
"""
Make temporary copy of test notebook before it's operated on, then revert it.
"""
Expand Down
10 changes: 9 additions & 1 deletion tests/test_black.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import difflib
import os
from typing import TYPE_CHECKING

import pytest

from nbqa.__main__ import main

if TYPE_CHECKING:
from pathlib import Path

def test_black_works(tmp_notebook_for_testing, capsys):
from _pytest.capture import CaptureFixture


def test_black_works(
tmp_notebook_for_testing: "Path", capsys: "CaptureFixture"
) -> None:
"""
Check black works. Should only reformat code cells.
"""
Expand Down
6 changes: 5 additions & 1 deletion tests/test_create_blank_init_files.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import os
from pathlib import Path
from typing import TYPE_CHECKING

from nbqa.__main__ import _create_blank_init_files

if TYPE_CHECKING:
from py._path.local import LocalPath

def test_create_blank_init_files(tmpdir):

def test_create_blank_init_files(tmpdir: "LocalPath") -> None:
"""
Check that if a notebook is in current working directory then no init file is made.
"""
Expand Down
8 changes: 7 additions & 1 deletion tests/test_doctest.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import difflib
import os
from pathlib import Path
from typing import TYPE_CHECKING

import pytest

from nbqa.__main__ import main

if TYPE_CHECKING:
from _pytest.capture import CaptureFixture

def test_pytest_doctest_works(tmp_notebook_for_testing, capsys):

def test_pytest_doctest_works(
tmp_notebook_for_testing: Path, capsys: "CaptureFixture"
) -> None:
"""
Check pytest --doctest-modules works.
"""
Expand Down
10 changes: 9 additions & 1 deletion tests/test_flake8_works.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import difflib
import os
from textwrap import dedent
from typing import TYPE_CHECKING

import pytest

from nbqa.__main__ import main

if TYPE_CHECKING:
from pathlib import Path

def test_flake8_works(tmp_notebook_for_testing, capsys):
from _pytest.capture import CaptureFixture


def test_flake8_works(
tmp_notebook_for_testing: "Path", capsys: "CaptureFixture"
) -> None:
"""
Check flake8 works. Shouldn't alter the notebook content.
"""
Expand Down

0 comments on commit f16c7b4

Please sign in to comment.