11"""Normalize and copy an executed Jupyter notebook.
22
33Strips execution metadata (execution counts, timestamps) that change between
4- runs and copies the normalized notebook to the target path. Used by CI to
5- produce a clean diff when deciding whether to open a PR.
4+ runs and copies the normalized notebook to the target path. Also strips version
5+ numbers from CDN resource URLs so that version bumps in upstream packages do
6+ not trigger new PRs to update the .ipynb output.
67
78Usage: python tests/normalize-notebook.py <source.ipynb> <target.ipynb>
89"""
9- import json , sys
10+ import json , re , sys
11+
12+
13+ def strip_cdn_versions (s : str ) -> str :
14+ """Remove version specifiers (e.g. @5, @v0.14.34) from CDN URLs in src/href attributes."""
15+ return re .sub (r'((src|href)="https://[^"]*?)/@v?\d+(?:\.\d+)*' , r'\1' , s )
16+
17+
18+ def normalize_value (v ):
19+ if isinstance (v , str ):
20+ return strip_cdn_versions (v )
21+ if isinstance (v , list ):
22+ return [normalize_value (x ) for x in v ]
23+ return v
1024
1125
1226def normalize (nb : dict ) -> None :
@@ -15,6 +29,13 @@ def normalize(nb: dict) -> None:
1529 cell .get ('metadata' , {}).pop ('execution' , None )
1630 for out in cell .get ('outputs' , []):
1731 out .pop ('execution_count' , None )
32+ for key in ('text' , 'data' ):
33+ if key in out :
34+ val = out [key ]
35+ if isinstance (val , list ):
36+ out [key ] = [normalize_value (x ) for x in val ]
37+ elif isinstance (val , dict ):
38+ out [key ] = {k : normalize_value (v ) for k , v in val .items ()}
1839
1940
2041if __name__ == '__main__' :
0 commit comments