RimAlMoatassime commited on
Commit
9f3a72f
·
verified ·
1 Parent(s): 54a0870

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +23 -0
  2. app.py +245 -0
  3. requirements.txt +8 -0
README.md ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Interactive Notebook Runner
3
+ emoji: 📓
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: gradio
7
+ app_file: app.py
8
+ pinned: false
9
+ ---
10
+
11
+ # Interactive Notebook Runner
12
+
13
+ Upload a Jupyter notebook and two CSV files, preview them, then run the notebook.
14
+
15
+ ## Important build note
16
+ Do **not** pin `gradio` in `requirements.txt` on this Space template.
17
+ Hugging Face already installs a managed Gradio version during the build.
18
+
19
+ ## How to use
20
+ 1. Upload a notebook in the app, or add `analysis_notebook.ipynb` to the Space root.
21
+ 2. Upload two CSV files in the app, or add the default CSVs to the Space root.
22
+ 3. Adjust the execution filenames if your notebook expects specific names.
23
+ 4. Click **Run notebook**.
app.py ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AI-Assisted Code — Academic Integrity Notice
2
+ # Generated with The App Builder. ESCP coursework.
3
+ # Student must be able to explain all code when asked.
4
+
5
+ """Interactive Hugging Face Space to run a Jupyter notebook on two CSV files."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import io
10
+ import os
11
+ import re
12
+ import shutil
13
+ import tempfile
14
+ import traceback
15
+ import zipfile
16
+ from pathlib import Path
17
+
18
+ import gradio as gr
19
+ import nbformat
20
+ import pandas as pd
21
+ from nbclient import NotebookClient
22
+
23
+
24
+ DEFAULT_NOTEBOOK = "analysis_notebook.ipynb"
25
+ DEFAULT_CSV_1 = "synthetic_book_reviews.csv"
26
+ DEFAULT_CSV_2 = "synthetic_sales_data.csv"
27
+
28
+
29
+ def locate_default_file(file_name: str) -> Path | None:
30
+ """Return a default file path if it exists in the Space root."""
31
+ candidate = Path(file_name)
32
+ return candidate if candidate.exists() else None
33
+
34
+
35
+ def safe_copy(source_path: str | Path, target_path: Path) -> None:
36
+ """Copy a file into the execution folder."""
37
+ shutil.copy2(str(source_path), str(target_path))
38
+
39
+
40
+ def read_csv_preview(file_obj, rows: int = 8) -> pd.DataFrame:
41
+ """Load a preview of a CSV file for the UI."""
42
+ if file_obj is None:
43
+ return pd.DataFrame({"message": ["No file selected."]})
44
+ return pd.read_csv(file_obj.name).head(rows)
45
+
46
+
47
+ def prepare_inputs(
48
+ notebook_file,
49
+ csv_file_1,
50
+ csv_file_2,
51
+ notebook_name: str,
52
+ csv_name_1: str,
53
+ csv_name_2: str,
54
+ ) -> tuple[Path, Path]:
55
+ """Create a temporary run folder and place notebook/data files inside it."""
56
+ run_dir = Path(tempfile.mkdtemp(prefix="hf_notebook_run_"))
57
+
58
+ resolved_notebook = notebook_file.name if notebook_file else None
59
+ resolved_csv_1 = csv_file_1.name if csv_file_1 else None
60
+ resolved_csv_2 = csv_file_2.name if csv_file_2 else None
61
+
62
+ if resolved_notebook is None:
63
+ default_notebook = locate_default_file(DEFAULT_NOTEBOOK)
64
+ if default_notebook is None:
65
+ raise FileNotFoundError(
66
+ "Notebook missing. Upload a .ipynb file in the app or add "
67
+ f"{DEFAULT_NOTEBOOK} to the Space root."
68
+ )
69
+ resolved_notebook = default_notebook
70
+
71
+ if resolved_csv_1 is None:
72
+ default_csv_1 = locate_default_file(DEFAULT_CSV_1)
73
+ if default_csv_1 is None:
74
+ raise FileNotFoundError(
75
+ "CSV 1 missing. Upload a CSV in the app or add "
76
+ f"{DEFAULT_CSV_1} to the Space root."
77
+ )
78
+ resolved_csv_1 = default_csv_1
79
+
80
+ if resolved_csv_2 is None:
81
+ default_csv_2 = locate_default_file(DEFAULT_CSV_2)
82
+ if default_csv_2 is None:
83
+ raise FileNotFoundError(
84
+ "CSV 2 missing. Upload a CSV in the app or add "
85
+ f"{DEFAULT_CSV_2} to the Space root."
86
+ )
87
+ resolved_csv_2 = default_csv_2
88
+
89
+ notebook_target = run_dir / notebook_name.strip()
90
+ csv_target_1 = run_dir / csv_name_1.strip()
91
+ csv_target_2 = run_dir / csv_name_2.strip()
92
+
93
+ safe_copy(resolved_notebook, notebook_target)
94
+ safe_copy(resolved_csv_1, csv_target_1)
95
+ safe_copy(resolved_csv_2, csv_target_2)
96
+
97
+ return run_dir, notebook_target
98
+
99
+
100
+ def clean_notebook_cells(notebook) -> None:
101
+ """Remove shell install commands that often break in managed Spaces."""
102
+ filtered_cells = []
103
+ for cell in notebook.cells:
104
+ if cell.get("cell_type") != "code":
105
+ filtered_cells.append(cell)
106
+ continue
107
+ source = cell.get("source", "")
108
+ if "!pip install" in source or "%pip install" in source:
109
+ continue
110
+ filtered_cells.append(cell)
111
+ notebook.cells = filtered_cells
112
+
113
+
114
+ def collect_text_output(executed_notebook) -> str:
115
+ """Extract text outputs from notebook cells for display in the UI."""
116
+ collected = []
117
+ for index, cell in enumerate(executed_notebook.cells, start=1):
118
+ if cell.get("cell_type") != "code":
119
+ continue
120
+ outputs = cell.get("outputs", [])
121
+ for output in outputs:
122
+ if output.get("output_type") == "stream":
123
+ text = output.get("text", "").strip()
124
+ if text:
125
+ collected.append(f"Cell {index}\n{text}")
126
+ if output.get("output_type") == "execute_result":
127
+ data = output.get("data", {})
128
+ text = data.get("text/plain", "")
129
+ if text:
130
+ collected.append(f"Cell {index}\n{text}")
131
+ return "\n\n" + ("\n\n".join(collected[:20]) if collected else "No text output captured.")
132
+
133
+
134
+ def build_output_zip(run_dir: Path) -> str:
135
+ """Zip run artifacts so the user can download them."""
136
+ zip_path = run_dir / "run_outputs.zip"
137
+ with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as archive:
138
+ for item in run_dir.iterdir():
139
+ if item.is_file():
140
+ archive.write(item, arcname=item.name)
141
+ return str(zip_path)
142
+
143
+
144
+ def run_notebook(
145
+ notebook_file,
146
+ csv_file_1,
147
+ csv_file_2,
148
+ notebook_name: str,
149
+ csv_name_1: str,
150
+ csv_name_2: str,
151
+ ):
152
+ """Execute the notebook and return logs, summary, and downloadable files."""
153
+ try:
154
+ run_dir, notebook_path = prepare_inputs(
155
+ notebook_file,
156
+ csv_file_1,
157
+ csv_file_2,
158
+ notebook_name,
159
+ csv_name_1,
160
+ csv_name_2,
161
+ )
162
+
163
+ with open(notebook_path, "r", encoding="utf-8") as handle:
164
+ notebook = nbformat.read(handle, as_version=4)
165
+
166
+ clean_notebook_cells(notebook)
167
+ client = NotebookClient(notebook, timeout=900, kernel_name="python3")
168
+ executed_notebook = client.execute(cwd=str(run_dir))
169
+
170
+ executed_path = run_dir / "executed_notebook.ipynb"
171
+ with open(executed_path, "w", encoding="utf-8") as handle:
172
+ nbformat.write(executed_notebook, handle)
173
+
174
+ summary = collect_text_output(executed_notebook)
175
+ output_zip = build_output_zip(run_dir)
176
+ log = "Execution successful."
177
+ return log, summary, str(executed_path), output_zip
178
+
179
+ except Exception:
180
+ error_text = traceback.format_exc()
181
+ return f"Execution failed.\n\n{error_text}", "", None, None
182
+
183
+
184
+ with gr.Blocks(title="Interactive Notebook Runner") as demo:
185
+ gr.Markdown(
186
+ """
187
+ # Interactive Notebook Runner
188
+
189
+ Upload a Jupyter notebook and two CSV files, preview the datasets,
190
+ choose the execution file names, then run the notebook inside the Space.
191
+ """
192
+ )
193
+
194
+ with gr.Row():
195
+ notebook_input = gr.File(label="Notebook (.ipynb)", file_types=[".ipynb"])
196
+ csv_input_1 = gr.File(label="CSV file 1", file_types=[".csv"])
197
+ csv_input_2 = gr.File(label="CSV file 2", file_types=[".csv"])
198
+
199
+ with gr.Row():
200
+ notebook_name = gr.Textbox(
201
+ label="Notebook filename during execution",
202
+ value=DEFAULT_NOTEBOOK,
203
+ )
204
+ csv_name_1 = gr.Textbox(
205
+ label="CSV 1 filename during execution",
206
+ value=DEFAULT_CSV_1,
207
+ )
208
+ csv_name_2 = gr.Textbox(
209
+ label="CSV 2 filename during execution",
210
+ value=DEFAULT_CSV_2,
211
+ )
212
+
213
+ preview_button = gr.Button("Refresh previews")
214
+ with gr.Row():
215
+ preview_1 = gr.Dataframe(label="Preview CSV 1")
216
+ preview_2 = gr.Dataframe(label="Preview CSV 2")
217
+
218
+ run_button = gr.Button("Run notebook")
219
+ status_box = gr.Textbox(label="Execution log", lines=14)
220
+ summary_box = gr.Textbox(label="Notebook output summary", lines=14)
221
+ executed_file = gr.File(label="Executed notebook")
222
+ zip_file = gr.File(label="Download outputs ZIP")
223
+
224
+ preview_button.click(
225
+ fn=lambda a, b: (read_csv_preview(a), read_csv_preview(b)),
226
+ inputs=[csv_input_1, csv_input_2],
227
+ outputs=[preview_1, preview_2],
228
+ )
229
+
230
+ run_button.click(
231
+ fn=run_notebook,
232
+ inputs=[
233
+ notebook_input,
234
+ csv_input_1,
235
+ csv_input_2,
236
+ notebook_name,
237
+ csv_name_1,
238
+ csv_name_2,
239
+ ],
240
+ outputs=[status_box, summary_box, executed_file, zip_file],
241
+ )
242
+
243
+
244
+ if __name__ == "__main__":
245
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ pandas==2.2.2
2
+ nbformat==5.10.4
3
+ nbclient==0.10.0
4
+ ipykernel==6.29.5
5
+ jupyter-core==5.7.2
6
+ vaderSentiment==3.3.2
7
+ matplotlib==3.9.2
8
+ numpy==2.1.1