Skip to content

Commit fc432c4

Browse files
authored
Merge pull request #49 from neo4j/format-notebooks
Clean notebooks
2 parents dbb2473 + 19a3e23 commit fc432c4

File tree

6 files changed

+151
-50
lines changed

6 files changed

+151
-50
lines changed

examples/gds-nvl-example.ipynb

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
},
2727
{
2828
"cell_type": "code",
29-
"execution_count": 71,
29+
"execution_count": null,
3030
"metadata": {},
3131
"outputs": [],
3232
"source": [
@@ -76,7 +76,7 @@
7676
},
7777
{
7878
"cell_type": "code",
79-
"execution_count": 74,
79+
"execution_count": null,
8080
"metadata": {},
8181
"outputs": [],
8282
"source": [
@@ -89,7 +89,7 @@
8989
"metadata": {},
9090
"outputs": [],
9191
"source": [
92-
"G_sample.node_properties()\n"
92+
"G_sample.node_properties()"
9393
]
9494
},
9595
{
@@ -101,14 +101,19 @@
101101
},
102102
{
103103
"cell_type": "code",
104-
"execution_count": 76,
104+
"execution_count": null,
105105
"metadata": {},
106106
"outputs": [],
107107
"source": [
108108
"from neo4j_viz.gds import from_gds\n",
109109
"\n",
110110
"# TODO inform about automatic size scaling\n",
111-
"VG = from_gds(gds, G_sample, size_property=\"pagerank\", additional_node_properties=[\"componentId\", \"subject\"])"
111+
"VG = from_gds(\n",
112+
" gds,\n",
113+
" G_sample,\n",
114+
" size_property=\"pagerank\",\n",
115+
" additional_node_properties=[\"componentId\", \"subject\"],\n",
116+
")"
112117
]
113118
},
114119
{
@@ -129,7 +134,7 @@
129134
},
130135
{
131136
"cell_type": "code",
132-
"execution_count": 78,
137+
"execution_count": null,
133138
"metadata": {},
134139
"outputs": [],
135140
"source": [
@@ -143,7 +148,7 @@
143148
"metadata": {},
144149
"outputs": [],
145150
"source": [
146-
"VG.render()\n"
151+
"VG.render()"
147152
]
148153
},
149154
{
@@ -264,7 +269,7 @@
264269
},
265270
{
266271
"cell_type": "code",
267-
"execution_count": 86,
272+
"execution_count": null,
268273
"metadata": {},
269274
"outputs": [],
270275
"source": [
@@ -296,22 +301,8 @@
296301
}
297302
],
298303
"metadata": {
299-
"kernelspec": {
300-
"display_name": ".venv311",
301-
"language": "python",
302-
"name": "python3"
303-
},
304304
"language_info": {
305-
"codemirror_mode": {
306-
"name": "ipython",
307-
"version": 3
308-
},
309-
"file_extension": ".py",
310-
"mimetype": "text/x-python",
311-
"name": "python",
312-
"nbconvert_exporter": "python",
313-
"pygments_lexer": "ipython3",
314-
"version": "3.11.9"
305+
"name": "python"
315306
}
316307
},
317308
"nbformat": 4,

examples/neo4j-nvl-example.ipynb

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@
5050
" driver.verify_connectivity()\n",
5151
"\n",
5252
" result = driver.execute_query(\n",
53-
" \"MATCH (n)-[r]-(m) RETURN n,r,m LIMIT 10\", database_=\"neo4j\", result_transformer_=Result.graph\n",
53+
" \"MATCH (n)-[r]-(m) RETURN n,r,m LIMIT 10\",\n",
54+
" database_=\"neo4j\",\n",
55+
" result_transformer_=Result.graph,\n",
5456
" )\n",
5557
"\n",
5658
" result\n",
@@ -102,7 +104,9 @@
102104
"cell_type": "markdown",
103105
"id": "a28bd5aa",
104106
"metadata": {},
105-
"source": "Now, we can render our result with NVL the following way:\n"
107+
"source": [
108+
"Now, we can render our result with NVL the following way:\n"
109+
]
106110
},
107111
{
108112
"cell_type": "code",
@@ -119,12 +123,14 @@
119123
]
120124
},
121125
{
122-
"metadata": {},
123126
"cell_type": "code",
124-
"outputs": [],
125127
"execution_count": null,
126-
"source": "VG.color_nodes(\"caption\")",
127-
"id": "46aaa12317299ad0"
128+
"id": "46aaa12317299ad0",
129+
"metadata": {},
130+
"outputs": [],
131+
"source": [
132+
"VG.color_nodes(\"caption\")"
133+
]
128134
},
129135
{
130136
"cell_type": "code",
@@ -164,22 +170,8 @@
164170
}
165171
],
166172
"metadata": {
167-
"kernelspec": {
168-
"display_name": ".venv311",
169-
"language": "python",
170-
"name": "python3"
171-
},
172173
"language_info": {
173-
"codemirror_mode": {
174-
"name": "ipython",
175-
"version": 3
176-
},
177-
"file_extension": ".py",
178-
"mimetype": "text/x-python",
179-
"name": "python",
180-
"nbconvert_exporter": "python",
181-
"pygments_lexer": "ipython3",
182-
"version": "3.11.9"
174+
"name": "python"
183175
}
184176
},
185177
"nbformat": 4,

python-wrapper/pyproject.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ dev = [
4646
"selenium==4.27.1",
4747
"palettable==3.3.3",
4848
"pytest-mock==3.14.0",
49+
"nbconvert==7.16.5",
4950
]
5051
pandas = ["pandas>=2, <3", "pandas-stubs>=2, <3"]
5152
gds = ["graphdatascience>=1, <2"] # not compatible yet with Python 3.13
@@ -128,5 +129,9 @@ select = [
128129

129130
[tool.mypy]
130131
strict = true
131-
exclude = '(^build|^\.?venv)'
132+
exclude = [
133+
'(^build|^\.?venv)',
134+
'build',
135+
]
132136
plugins = ['pydantic.mypy']
137+
untyped_calls_exclude=["nbconvert"]

scripts/checkstyle.sh

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,32 @@
11
#!/usr/bin/env bash
2-
3-
cd python-wrapper
2+
GIT_ROOT=$(git rev-parse --show-toplevel)
43

54
set -o errexit
65
set -o nounset
76
set -o pipefail
87

98
python -m ruff check .
109
python -m ruff format --check .
11-
mypy .
10+
mypy --config-file python-wrapper/pyproject.toml .
11+
12+
13+
if [ "${SKIP_NOTEBOOKS:-false}" == "true" ]; then
14+
echo "Skipping notebooks"
15+
exit 0
16+
fi
17+
18+
echo "Checking notebooks"
19+
20+
NOTEBOOKS="${GIT_ROOT}/examples/*.ipynb" # ./examples/dev/*.ipynb"
21+
for f in $NOTEBOOKS
22+
do
23+
NB=$(cat $f)
24+
FORMATTED_NB=$(python "${GIT_ROOT}/scripts/clean_notebooks.py" -i "$f" -o stdout)
25+
26+
if [[ "$FORMATTED_NB" != "$NB" ]];
27+
then
28+
echo "Notebook $f is not correctly formatted. See diff above for more details."
29+
diff --color=always --suppress-common-lines --minimal --side-by-side <(echo "$NB") <(echo "$FORMATTED_NB")
30+
exit 1
31+
fi
32+
done

scripts/clean_notebooks.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# reasons for not using nbconvert cli tool:
2+
# * cannot keep output based on a given tag
3+
4+
import argparse
5+
import logging
6+
from enum import Enum
7+
from pathlib import Path
8+
from typing import Any
9+
10+
import nbconvert
11+
from nbconvert.preprocessors import Preprocessor
12+
13+
PRESERVE_CELL_OUTPUT_KEY = "preserve-output"
14+
METADATA_TAG_KEY = "tags"
15+
16+
17+
class OutputMode(Enum):
18+
STDOUT = "stdout"
19+
INPLACE = "inplace"
20+
21+
22+
class CustomClearOutputPreprocessor(Preprocessor):
23+
"""
24+
Removes the output from all code cells in a notebook.
25+
Option to keep cell output for cells with a given metadata tag
26+
"""
27+
28+
def preprocess_cell(
29+
self, cell: Any, resources: Any, cell_index: Any
30+
) -> tuple[Any, Any]:
31+
"""
32+
Apply a transformation on each cell. See base.py for details.
33+
"""
34+
if cell.cell_type == "code" and PRESERVE_CELL_OUTPUT_KEY not in cell[
35+
"metadata"
36+
].get(METADATA_TAG_KEY, []):
37+
cell.outputs = []
38+
cell.execution_count = None
39+
return cell, resources
40+
41+
42+
def main(input_path: Path, output_mode: OutputMode) -> None:
43+
logger = logging.getLogger("NotebookCleaner")
44+
logger.info(f"Cleaning notebooks from `{input_path}`, mode: `{output_mode}`")
45+
46+
exporter = nbconvert.NotebookExporter()
47+
48+
metadata_cleaner = nbconvert.preprocessors.ClearMetadataPreprocessor(
49+
preserve_cell_metadata_mask=METADATA_TAG_KEY
50+
)
51+
output_cleaner = CustomClearOutputPreprocessor() # type: ignore
52+
53+
exporter.register_preprocessor(metadata_cleaner, enabled=True)
54+
exporter.register_preprocessor(output_cleaner, enabled=True)
55+
56+
if input_path.is_file():
57+
notebooks = [input_path]
58+
else:
59+
notebooks = [
60+
f for f in input_path.iterdir() if f.is_file() and f.suffix == ".ipynb"
61+
]
62+
63+
logger.info(f"Formatting {len(notebooks)} notebooks.")
64+
65+
for notebook in notebooks:
66+
output = exporter.from_filename(str(notebook))
67+
68+
formatted_notebook = output[0]
69+
70+
if output_mode == OutputMode.INPLACE:
71+
with notebook.open(mode="w") as file:
72+
file.write(str(formatted_notebook))
73+
elif output_mode == OutputMode.STDOUT:
74+
print(formatted_notebook)
75+
76+
77+
if __name__ == "__main__":
78+
parser = argparse.ArgumentParser()
79+
parser.add_argument("-o", "--output", choices=[e.value for e in OutputMode])
80+
parser.add_argument(
81+
"-i", "--input", default="examples", help="path to the notebook file or folder"
82+
)
83+
84+
args = parser.parse_args()
85+
86+
main(Path(args.input), OutputMode(args.output))

scripts/makestyle.sh

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#!/usr/bin/env bash
2-
3-
cd python-wrapper
2+
GIT_ROOT=$(git rev-parse --show-toplevel)
43

54
set -o errexit
65
set -o nounset
@@ -9,3 +8,10 @@ set -o xtrace
98

109
python -m ruff format .
1110
python -m ruff check . --fix
11+
12+
if [ "${SKIP_NOTEBOOKS:-false}" == "true" ]; then
13+
echo "Skipping notebooks"
14+
exit 0
15+
fi
16+
17+
python "${GIT_ROOT}/scripts/clean_notebooks.py" -i "${GIT_ROOT}/examples/" -o inplace

0 commit comments

Comments
 (0)