Source code for flake8_nb.flake8_integration.formatter
"""Module containing the report formatter.
This also includes the code to map parsed error back to the
original notebook and the cell the code in.
"""
from __future__ import annotations
import os
from typing import cast
from flake8.formatting.default import Default
from flake8.style_guide import Violation
from flake8_nb.parsers.notebook_parsers import NotebookParser
from flake8_nb.parsers.notebook_parsers import map_intermediate_to_input
try:
from flake8.formatting.default import COLORS
from flake8.formatting.default import COLORS_OFF
except ImportError:
COLORS = COLORS_OFF = {}
[docs]def map_notebook_error(violation: Violation, format_str: str) -> tuple[str, int] | None:
"""Map the violation caused in an intermediate file back to its cause.
The cause is resolved as the notebook, the input cell and
the respective line number in that cell.
Parameters
----------
violation : Violation
Reported violation from checking the parsed notebook
format_str: str
Format string used to format the notebook path and cell reporting.
Returns
-------
tuple[str, int] | None
(filename, input_cell_line_number)
``filename`` being the name of the original notebook and
the input cell were the violation was reported.
``input_cell_line_number`` line number in the input cell
were the violation was reported.
"""
intermediate_filename = os.path.abspath(violation.filename)
intermediate_line_number = violation.line_number
mappings = NotebookParser.get_mappings()
for original_notebook, intermediate_py, input_line_mapping in mappings:
if os.path.samefile(intermediate_py, intermediate_filename):
input_id, input_cell_line_number = map_intermediate_to_input(
input_line_mapping, intermediate_line_number
)
exec_count, code_cell_count, total_cell_count = input_id
filename = format_str.format(
nb_path=original_notebook,
exec_count=exec_count,
code_cell_count=code_cell_count,
total_cell_count=total_cell_count,
)
return filename, input_cell_line_number
return None
[docs]class IpynbFormatter(Default): # type: ignore[misc]
r"""Default flake8_nb formatter for jupyter notebooks.
If the file to be formatted is a ``*.py`` file,
it uses flake8's default formatter.
"""
[docs] def after_init(self) -> None:
"""Check for a custom format string."""
if self.options.format.lower() != "default_notebook":
self.error_format = self.options.format
if not hasattr(self, "color"):
self.color = True
[docs] def format(self, violation: Violation) -> str | None:
r"""Format the error detected by a flake8 checker.
Depending on if the violation was caused by a ``*.py`` file
or by a parsed notebook.
Parameters
----------
violation : Violation
Error a checker reported.
Returns
-------
str | None
Formatted error message, which will be displayed
in the terminal.
"""
filename = violation.filename
if filename.lower().endswith(".ipynb_parsed"):
map_result = map_notebook_error(violation, self.options.notebook_cell_format)
if map_result:
filename, line_number = map_result
return cast(
str,
self.error_format
% {
"code": violation.code,
"text": violation.text,
"path": filename,
"row": line_number,
"col": violation.column_number,
**(COLORS if self.color else COLORS_OFF),
},
)
return cast(str, super().format(violation))