Skip to content

Commit 1ae9f0a

Browse files
authored
Merge pull request #14 from MicrosoftCloudEssentials-LearningHub/render-pipeline
Add workflow to format Jupyter notebooks for GitHub
2 parents 89d478f + 5cfed16 commit 1ae9f0a

16 files changed

Lines changed: 2316 additions & 1238 deletions

File tree

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Notebook to GitHub-Compatible Format Converter
4+
5+
This script fixes Jupyter notebooks for GitHub rendering by:
6+
1. Converting XML-format notebooks to standard Jupyter JSON format
7+
2. Cleaning widget metadata that can cause GitHub rendering issues
8+
"""
9+
10+
import os
11+
import json
12+
import re
13+
import nbformat
14+
from nbformat.validator import validate
15+
16+
def process_notebooks(directory="."):
17+
"""Find and process all notebook files in the repository"""
18+
notebook_files = []
19+
print(f"Searching for notebooks in directory: {directory}")
20+
for root, dirs, files in os.walk(directory):
21+
# Skip directories that should be excluded
22+
if '.git' in dirs:
23+
dirs.remove('.git') # Skip git directory
24+
if '.github' in dirs:
25+
dirs.remove('.github') # Skip GitHub directory
26+
if '.venv' in dirs:
27+
dirs.remove('.venv') # Skip virtual environments
28+
29+
for file in files:
30+
if file.endswith('.ipynb'):
31+
notebook_path = os.path.join(root, file)
32+
print(f"Found notebook: {notebook_path}")
33+
notebook_files.append(notebook_path)
34+
35+
print(f"Found {len(notebook_files)} notebooks to process")
36+
37+
success_count = 0
38+
for nb_path in notebook_files:
39+
if convert_notebook(nb_path):
40+
success_count += 1
41+
42+
print(f"Successfully rendered {success_count} out of {len(notebook_files)} notebooks")
43+
return success_count
44+
45+
def convert_notebook(filepath):
46+
"""Convert a notebook to GitHub-compatible format by cleaning widget metadata"""
47+
print(f"\nProcessing {filepath}")
48+
49+
try:
50+
# Read the notebook content
51+
with open(filepath, 'r', encoding='utf-8') as f:
52+
content = f.read()
53+
54+
# Check if this is a XML notebook
55+
if '<VSCode.Cell' in content:
56+
print(f" Converting from XML format...")
57+
# Extract cells using regex
58+
cells = []
59+
cell_pattern = re.compile(r'<VSCode\.Cell.*?language="(.*?)".*?>(.*?)</VSCode\.Cell>', re.DOTALL)
60+
61+
matches = list(cell_pattern.finditer(content))
62+
if not matches:
63+
print(f" WARNING: No cells found in {filepath}")
64+
return False
65+
66+
print(f" Found {len(matches)} cells")
67+
68+
for match in matches:
69+
cell_type, cell_content = match.groups()
70+
71+
if cell_type == "markdown":
72+
cells.append(nbformat.v4.new_markdown_cell(
73+
source=cell_content.strip()
74+
))
75+
else: # python, javascript, etc.
76+
cells.append(nbformat.v4.new_code_cell(
77+
source=cell_content.strip()
78+
))
79+
80+
# Create a new notebook
81+
nb = nbformat.v4.new_notebook()
82+
nb.cells = cells
83+
84+
# Add required metadata
85+
nb.metadata = {
86+
"kernelspec": {
87+
"display_name": "Python 3",
88+
"language": "python",
89+
"name": "python3"
90+
},
91+
"language_info": {
92+
"codemirror_mode": {
93+
"name": "ipython",
94+
"version": 3
95+
},
96+
"file_extension": ".py",
97+
"mimetype": "text/x-python",
98+
"name": "python",
99+
"nbconvert_exporter": "python",
100+
"pygments_lexer": "ipython3",
101+
"version": "3.8.10"
102+
},
103+
# Add empty widget state to prevent GitHub rendering issues
104+
"widgets": {
105+
"application/vnd.jupyter.widget-state+json": {
106+
"state": {},
107+
"version_major": 2,
108+
"version_minor": 0
109+
}
110+
}
111+
}
112+
113+
# Validate and write the notebook
114+
validate(nb)
115+
with open(filepath, 'w', encoding='utf-8') as f:
116+
nbformat.write(nb, f)
117+
118+
print(f" Successfully rendered {filepath} for GitHub compatibility")
119+
return True
120+
121+
else:
122+
# It's already in JSON format, clean widget metadata
123+
try:
124+
notebook = json.loads(content)
125+
print(f" Cleaning widget metadata...")
126+
127+
# Remove potentially problematic widget state but keep proper structure
128+
if 'metadata' in notebook:
129+
# Replace with clean widget state
130+
notebook['metadata']['widgets'] = {
131+
"application/vnd.jupyter.widget-state+json": {
132+
"state": {},
133+
"version_major": 2,
134+
"version_minor": 0
135+
}
136+
}
137+
138+
# Clean widget metadata from cells as well
139+
for cell in notebook.get('cells', []):
140+
if 'metadata' in cell and 'widgets' in cell['metadata']:
141+
del cell['metadata']['widgets']
142+
143+
# Write the cleaned notebook
144+
with open(filepath, 'w', encoding='utf-8') as f:
145+
json.dump(notebook, f, indent=2)
146+
147+
print(f" Successfully cleaned {filepath} for GitHub compatibility")
148+
return True
149+
150+
except json.JSONDecodeError:
151+
print(f" ERROR: {filepath} is not in valid JSON format or XML format")
152+
return False
153+
154+
except Exception as e:
155+
print(f" ERROR processing {filepath}: {str(e)}")
156+
return False
157+
158+
if __name__ == "__main__":
159+
print("Rendering notebooks for GitHub compatibility...")
160+
# Get the repository root directory from environment variable if available
161+
repo_root = os.environ.get('GITHUB_WORKSPACE', '.')
162+
print(f"Repository root: {repo_root}")
163+
process_notebooks(repo_root)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Render Notebooks for GitHub
2+
3+
on:
4+
push:
5+
paths:
6+
- '**.ipynb'
7+
pull_request:
8+
branches:
9+
- main
10+
workflow_dispatch: # Allows manual triggering
11+
12+
permissions:
13+
contents: write
14+
pull-requests: write
15+
16+
jobs:
17+
render-notebooks:
18+
runs-on: ubuntu-latest
19+
20+
steps:
21+
- name: Checkout repository
22+
uses: actions/checkout@v4
23+
with:
24+
fetch-depth: 0
25+
ref: ${{ github.head_ref || github.ref_name }} # Explicitly checkout the branch that triggered the workflow
26+
27+
- name: Set up Python
28+
uses: actions/setup-python@v4
29+
with:
30+
python-version: '3.10'
31+
32+
- name: Install dependencies
33+
run: |
34+
python -m pip install --upgrade pip
35+
pip install nbformat nbconvert jupyter
36+
37+
- name: Run conversion script
38+
run: python .github/workflows/convert_notebooks.py
39+
40+
- name: Configure Git
41+
run: |
42+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
43+
git config --global user.name "github-actions[bot]"
44+
45+
- name: Commit and push changes
46+
run: |
47+
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
48+
echo "Current branch: $CURRENT_BRANCH"
49+
50+
# Stage all notebook files
51+
git add "**/*.ipynb"
52+
53+
# Check if there are changes to commit
54+
if git diff --staged --quiet; then
55+
echo "No changes detected in notebooks"
56+
else
57+
echo "Changes detected in notebooks"
58+
git commit -m "Fix notebooks for GitHub compatibility"
59+
git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git HEAD:${CURRENT_BRANCH}
60+
echo "Successfully pushed changes"
61+
fi

0_Overview.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Costa Rica
55
[![GitHub](https://img.shields.io/badge/--181717?logo=github&logoColor=ffffff)](https://github.com/)
66
[brown9804](https://github.com/brown9804)
77

8-
Last updated: 2025-07-16
8+
Last updated: 2025-09-11
99

1010
------------------------------------------
1111

@@ -104,7 +104,7 @@ Click here for more information about: [Z-Order & V-Order](https://github.com/Mi
104104

105105
<!-- START BADGE -->
106106
<div align="center">
107-
<img src="https://img.shields.io/badge/Total%20views-522-limegreen" alt="Total views">
108-
<p>Refresh Date: 2025-07-16</p>
107+
<img src="https://img.shields.io/badge/Total%20views-1474-limegreen" alt="Total views">
108+
<p>Refresh Date: 2025-09-11</p>
109109
</div>
110110
<!-- END BADGE -->

AzurePortal/1_MedallionArch/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Costa Rica
55
[![GitHub](https://img.shields.io/badge/--181717?logo=github&logoColor=ffffff)](https://github.com/)
66
[brown9804](https://github.com/brown9804)
77

8-
Last updated: 2025-07-16
8+
Last updated: 2025-09-11
99

1010
------------------------------------------
1111

@@ -299,7 +299,7 @@ https://github.com/user-attachments/assets/2a64762a-f120-4448-b0fb-7a49f4d1bedb
299299

300300
<!-- START BADGE -->
301301
<div align="center">
302-
<img src="https://img.shields.io/badge/Total%20views-522-limegreen" alt="Total views">
303-
<p>Refresh Date: 2025-07-16</p>
302+
<img src="https://img.shields.io/badge/Total%20views-1474-limegreen" alt="Total views">
303+
<p>Refresh Date: 2025-09-11</p>
304304
</div>
305305
<!-- END BADGE -->

AzurePortal/1_MedallionArch/docs/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Costa Rica
55
[![GitHub](https://img.shields.io/badge/--181717?logo=github&logoColor=ffffff)](https://github.com/)
66
[brown9804](https://github.com/brown9804)
77

8-
Last updated: 2025-07-16
8+
Last updated: 2025-09-11
99

1010
------------------------------------------
1111

@@ -15,7 +15,7 @@ Last updated: 2025-07-16
1515

1616
<!-- START BADGE -->
1717
<div align="center">
18-
<img src="https://img.shields.io/badge/Total%20views-522-limegreen" alt="Total views">
19-
<p>Refresh Date: 2025-07-16</p>
18+
<img src="https://img.shields.io/badge/Total%20views-1474-limegreen" alt="Total views">
19+
<p>Refresh Date: 2025-09-11</p>
2020
</div>
2121
<!-- END BADGE -->

0 commit comments

Comments
 (0)