Skip to content

Commit 79b5d58

Browse files
authored
Add Jupyter notebook support (#2)
1 parent b49f8d8 commit 79b5d58

20 files changed

+670
-78
lines changed

.flake8

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
max-complexity = 10
33
max-line-length = 88
44
ignore = E203, W503
5+
exclude = tests/resources/*

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
exclude: '^tests/resources/.*$' # ignore files in that folder because they are designed that way
12
repos:
23
- repo: https://github.com/pre-commit/pre-commit-hooks
34
rev: v4.3.0

.pre-commit-hooks.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
- id: format-def-indent
2-
name: Format indentation in function definitions
2+
name: Format indentation in function definitions (in .py files)
33
description: Make sure arguments in Python function definitions are indented by 8 spaces.
44
entry: format-def-indent
55
language: python
66
types: [python]
7+
- id: format-def-indent-in-jupyter
8+
name: Format indentation in function definitions (in .ipynb files)
9+
description: In Jupyter notebooks, make sure arguments in Python function definitions are indented by 8 spaces.
10+
entry: format-def-indent-in-jupyter
11+
language: python
12+
files: '^.*\.ipynb$'

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,37 @@ pip install format-def-indent
2020

2121
### As a command line tool
2222

23+
To auto-format Python files (.py):
24+
2325
```bash
2426
format-def-indent <PATH_THAT_CONTAINS_PYTHON_FILES>
2527
```
2628
Use `--help` to see documentations of command line arguments.
2729

30+
To auto-format Jupyter notebooks (.ipynb):
31+
32+
```bash
33+
format-def-indent-in-jupyter <PATH_THAT_CONTAINS_PYTHON_FILES>
34+
```
35+
2836
### As a pre-commit hook
2937

30-
Put the following into your `.pre-commit-config.yaml` file. Remember to replace `<VERSION>` with your version of this tool (such as `v0.1.4`):
38+
To auto-format Python files (.py), put the following into your `.pre-commit-config.yaml` file. Remember to replace `<VERSION>` with your version of this tool (such as `v0.1.4`):
3139
```yaml
3240
- repo: https://github.com/cyyc1/py-def-indent-formatter
3341
rev: <VERSION>
3442
hooks:
3543
- id: format-def-indent
3644
```
45+
46+
To auto-format Jupyter notebooks (.ipynb), put the following into your `.pre-commit-config.yaml` file:
47+
```yaml
48+
- repo: https://github.com/cyyc1/py-def-indent-formatter
49+
rev: <VERSION>
50+
hooks:
51+
- id: format-def-indent-in-jupyter
52+
```
53+
3754
See [pre-commit](https://github.com/pre-commit/pre-commit) for more instructions.
3855

3956
## What does this formatter do

format_def_indent/__main__.py

Lines changed: 0 additions & 4 deletions
This file was deleted.

format_def_indent/_base_fixer.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import argparse
2+
3+
from pathlib import Path
4+
5+
6+
class BaseFixer:
7+
def __init__(self, path: str, cli_args: argparse.Namespace) -> None:
8+
self.path = path
9+
self.cli_args = cli_args
10+
11+
def fix_one_directory_or_one_file(self) -> int:
12+
path_obj = Path(self.path)
13+
14+
if path_obj.is_file():
15+
return self.fix_one_file(path_obj.as_posix())
16+
17+
filenames = sorted(path_obj.rglob('*.py'))
18+
all_status = set()
19+
for filename in filenames:
20+
status = self.fix_one_file(filename)
21+
all_status.add(status)
22+
23+
return 0 if all_status == {0} else 1
24+
25+
def fix_one_file(self, *varargs, **kwargs):
26+
raise NotImplementedError('Please implement this method')

format_def_indent/_main.py

Lines changed: 0 additions & 70 deletions
This file was deleted.

format_def_indent/_main_jupyter.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import argparse
2+
import json
3+
import sys
4+
from typing import Sequence, Optional
5+
6+
from jupyter_notebook_parser import JupyterNotebookParser, JupyterNotebookRewriter
7+
8+
import format_def_indent._helper as helper
9+
from format_def_indent._base_fixer import BaseFixer
10+
11+
12+
class JupyterNotebookFixer(BaseFixer):
13+
def __init__(self, path: str, cli_args: argparse.Namespace) -> None:
14+
super().__init__(path=path, cli_args=cli_args)
15+
16+
def fix_one_file(self, filename: str) -> int:
17+
try:
18+
parsed = JupyterNotebookParser(filename)
19+
rewriter = JupyterNotebookRewriter(parsed_notebook=parsed)
20+
code_cells = parsed.get_code_cells()
21+
code_cell_indices = parsed.get_code_cell_indices()
22+
code_cell_sources = parsed.get_code_cell_sources()
23+
except Exception as exc:
24+
print(f'Error reading {filename}: {str(exc)}', file=sys.stderr)
25+
return 1
26+
else:
27+
ret_val = 0
28+
assert len(code_cells) == len(code_cell_indices)
29+
assert len(code_cells) == len(code_cell_sources)
30+
31+
for i in range(len(code_cells)):
32+
index: int = code_cell_indices[i]
33+
source: str = code_cell_sources[i]
34+
fixed: str = helper.fix_src(source_code=source)
35+
36+
if fixed != source:
37+
ret_val = 1
38+
rewriter.replace_source_in_code_cell(
39+
index=index,
40+
new_source=fixed,
41+
)
42+
43+
if ret_val == 1:
44+
print(f'Rewriting {filename}', file=sys.stderr)
45+
with open(filename, 'w') as fp:
46+
json.dump(parsed.notebook_content, fp, indent=1)
47+
# Jupyter notebooks (.ipynb) always ends with a new line
48+
# but json.dump does not.
49+
fp.write('\n')
50+
51+
return 0 if self.cli_args.exit_zero_even_if_changed else ret_val
52+
53+
54+
def main(argv: Optional[Sequence[str]] = None) -> int:
55+
parser = argparse.ArgumentParser()
56+
parser.add_argument('paths', nargs='*')
57+
parser.add_argument('--exit-zero-even-if-changed', action='store_true')
58+
args = parser.parse_args(argv)
59+
60+
ret = 0
61+
for path in args.paths:
62+
fixer = JupyterNotebookFixer(path=path, cli_args=args)
63+
ret |= fixer.fix_one_directory_or_one_file()
64+
65+
return ret
66+
67+
68+
if __name__ == '__main__':
69+
raise SystemExit(main())

format_def_indent/_main_py.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# This file is inspired by https://github.com/asottile/add-trailing-comma/blob/6be6dfc05176bddfc5a9c4bf0fd4941850f0fb41/add_trailing_comma/_main.py # noqa: E501
2+
3+
import argparse
4+
import sys
5+
6+
from typing import Sequence, Optional
7+
8+
import format_def_indent._helper as helper
9+
from format_def_indent._base_fixer import BaseFixer
10+
11+
12+
class PythonFileFixer(BaseFixer):
13+
def __init__(self, path: str, cli_args: argparse.Namespace) -> None:
14+
super().__init__(path=path, cli_args=cli_args)
15+
16+
def fix_one_file(self, filename: str) -> int:
17+
if filename == '-':
18+
source_bytes = sys.stdin.buffer.read()
19+
else:
20+
with open(filename, 'rb') as fb:
21+
source_bytes = fb.read()
22+
23+
try:
24+
source_text_orig = source_text = source_bytes.decode()
25+
except UnicodeDecodeError:
26+
msg = f'{filename} is non-utf-8 (not supported)'
27+
print(msg, file=sys.stderr)
28+
return 1
29+
30+
source_text = helper.fix_src(source_text)
31+
32+
if filename == '-':
33+
print(source_text, end='')
34+
elif source_text != source_text_orig:
35+
print(f'Rewriting {filename}', file=sys.stderr)
36+
with open(filename, 'wb') as f:
37+
f.write(source_text.encode())
38+
39+
if self.cli_args.exit_zero_even_if_changed:
40+
return 0
41+
else:
42+
return source_text != source_text_orig
43+
44+
45+
def main(argv: Optional[Sequence[str]] = None) -> int:
46+
parser = argparse.ArgumentParser()
47+
parser.add_argument('paths', nargs='*')
48+
parser.add_argument('--exit-zero-even-if-changed', action='store_true')
49+
args = parser.parse_args(argv)
50+
51+
ret = 0
52+
for path in args.paths:
53+
fixer = PythonFileFixer(path=path, cli_args=args)
54+
ret |= fixer.fix_one_directory_or_one_file()
55+
56+
return ret
57+
58+
59+
if __name__ == '__main__':
60+
raise SystemExit(main())

setup.cfg

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = format_def_indent
3-
version = 0.1.4
3+
version = 0.1.5
44
description = Make sure arguments in Python function definitions are indented by 8 spaces
55
long_description = file: README.md
66
long_description_content_type = text/markdown
@@ -15,6 +15,7 @@ classifiers =
1515
[options]
1616
packages = find:
1717
install_requires =
18+
jupyter-notebook-parser>=0.1.3
1819
tokenize-rt>=3.0.1
1920
python_requires = >=3.8
2021

@@ -25,7 +26,8 @@ exclude =
2526

2627
[options.entry_points]
2728
console_scripts =
28-
format-def-indent = format_def_indent._main:main
29+
format-def-indent = format_def_indent._main_py:main
30+
format-def-indent-in-jupyter = format_def_indent._main_jupyter:main
2931

3032
[bdist_wheel]
3133
universal = True

0 commit comments

Comments
 (0)