Skip to content

Commit 602d897

Browse files
Copilotmrjf
andauthored
Fix Python code errors in playground HTML files
- index-playground.html: Replace .to_list() with .tolist() for numpy array results from isin(), argsort(), isna(), notna() - ewm.html: Add raw=True to ewm().apply() for pandas 3.0 compatibility - nlargest.html: Replace nlargest/nsmallest on string Series with sort_values().head() which works in pandas Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: mrjf <180956+mrjf@users.noreply.github.com>
1 parent 03c3a87 commit 602d897

4 files changed

Lines changed: 152 additions & 7 deletions

File tree

playground/ewm.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ <h2>9 · Custom apply</h2>
548548

549549
# EWM weighted sum (equivalent to unadjusted numerator)
550550
# Note: pandas ewm.apply() does not pass weights; custom logic needed
551-
ewm_weighted_sum = s.ewm(alpha=0.5).apply(lambda vals: sum(vals))
551+
ewm_weighted_sum = s.ewm(alpha=0.5).apply(lambda vals: sum(vals), raw=True)
552552
print("weighted sum:", list(ewm_weighted_sum))</textarea>
553553
<div class="playground-output">Click ▶ Run to execute</div>
554554
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>

playground/index-playground.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ <h2>Label Look-up</h2>
318318
print("get_loc('b'):", idx.get_loc("b")) # unique → single int
319319
print("get_loc('a'):", idx.get_loc("a")) # duplicated → boolean mask
320320
print("contains 'c':", "c" in idx)
321-
print("isin(['a', 'c']):", idx.isin(["a", "c"]).to_list())</textarea>
321+
print("isin(['a', 'c']):", idx.isin(["a", "c"]).tolist())</textarea>
322322
<div class="playground-output">Click ▶ Run to execute</div>
323323
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
324324
</div>
@@ -386,7 +386,7 @@ <h2>Sorting &amp; Aggregation</h2>
386386
idx = pd.Index([30, 10, 20])
387387

388388
print("sort_values:", idx.sort_values().to_list())
389-
print("argsort: ", idx.argsort().to_list())
389+
print("argsort: ", idx.argsort().tolist())
390390
print("min:", idx.min())
391391
print("max:", idx.max())
392392
print("argmin:", idx.argmin())
@@ -456,8 +456,8 @@ <h2>Missing Values</h2>
456456

457457
idx = pd.Index([1, None, 3])
458458

459-
print("isna: ", idx.isna().to_list())
460-
print("notna:", idx.notna().to_list())
459+
print("isna: ", idx.isna().tolist())
460+
print("notna:", idx.notna().tolist())
461461
print("dropna:", idx.dropna().to_list())
462462
print("fillna(0):", idx.fillna(0).to_list())</textarea>
463463
<div class="playground-output">Click ▶ Run to execute</div>

playground/nlargest.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -531,8 +531,8 @@ <h2>8 · Edge cases</h2>
531531

532532
# string values — lexicographic order
533533
words = pd.Series(["banana", "apple", "cherry"])
534-
print("largest strings:", words.nlargest(2).tolist())
535-
print("smallest strings:", words.nsmallest(2).tolist())</textarea>
534+
print("largest strings:", words.sort_values(ascending=False).head(2).tolist())
535+
print("smallest strings:", words.sort_values().head(2).tolist())</textarea>
536536
<div class="playground-output">Click ▶ Run to execute</div>
537537
<div class="playground-hint">Ctrl+Enter to run · Tab to indent</div>
538538
</div>
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Validate Python/pandas examples from playground HTML pages.
4+
5+
Extracts all <textarea class="playground-python"> blocks from playground HTML
6+
files, runs each one with Python/pandas, and reports any failures.
7+
8+
Usage:
9+
python scripts/validate-python-examples.py [playground_dir]
10+
11+
Exit code 0 if all examples pass, 1 if any fail.
12+
"""
13+
14+
import html
15+
import os
16+
import re
17+
import subprocess
18+
import sys
19+
import tempfile
20+
import time
21+
22+
23+
def extract_python_blocks(html_file: str) -> list[tuple[str, str]]:
24+
"""Extract Python code blocks from a playground HTML file.
25+
26+
Returns a list of (section_label, code) tuples.
27+
"""
28+
with open(html_file, "r", encoding="utf-8") as f:
29+
content = f.read()
30+
31+
blocks: list[tuple[str, str]] = []
32+
33+
# Find all playground-python textareas
34+
pattern = re.compile(
35+
r'<textarea\s+class="playground-python"[^>]*>(.*?)</textarea>',
36+
re.DOTALL,
37+
)
38+
39+
# Also find section headings to label blocks
40+
section_pattern = re.compile(r"<h2>(.*?)</h2>", re.DOTALL)
41+
sections = section_pattern.findall(content)
42+
43+
for i, match in enumerate(pattern.finditer(content)):
44+
code = html.unescape(match.group(1))
45+
# Try to find the closest preceding section heading
46+
label = sections[i] if i < len(sections) else f"block_{i}"
47+
# Strip HTML tags from label
48+
label = re.sub(r"<[^>]+>", "", label).strip()
49+
blocks.append((label, code))
50+
51+
return blocks
52+
53+
54+
def run_python_block(code: str, label: str, html_file: str) -> tuple[bool, str, float]:
55+
"""Run a Python code block and return (success, output, elapsed_ms)."""
56+
with tempfile.NamedTemporaryFile(
57+
mode="w", suffix=".py", delete=False, encoding="utf-8"
58+
) as f:
59+
f.write(code)
60+
tmp_path = f.name
61+
62+
try:
63+
start = time.perf_counter()
64+
result = subprocess.run(
65+
[sys.executable, tmp_path],
66+
capture_output=True,
67+
text=True,
68+
timeout=30,
69+
)
70+
elapsed_ms = (time.perf_counter() - start) * 1000
71+
72+
if result.returncode != 0:
73+
return (
74+
False,
75+
f"STDERR:\n{result.stderr}\nSTDOUT:\n{result.stdout}",
76+
elapsed_ms,
77+
)
78+
return True, result.stdout, elapsed_ms
79+
except subprocess.TimeoutExpired:
80+
return False, "TIMEOUT (30s)", 0.0
81+
finally:
82+
os.unlink(tmp_path)
83+
84+
85+
def main() -> int:
86+
playground_dir = sys.argv[1] if len(sys.argv) > 1 else "playground"
87+
88+
if not os.path.isdir(playground_dir):
89+
print(f"Error: directory '{playground_dir}' not found", file=sys.stderr)
90+
return 1
91+
92+
html_files = sorted(
93+
f
94+
for f in os.listdir(playground_dir)
95+
if f.endswith(".html") and f != "index.html"
96+
)
97+
98+
total = 0
99+
passed = 0
100+
failed = 0
101+
failures: list[str] = []
102+
103+
for html_file in html_files:
104+
filepath = os.path.join(playground_dir, html_file)
105+
blocks = extract_python_blocks(filepath)
106+
107+
if not blocks:
108+
continue
109+
110+
print(f"\n{'='*60}")
111+
print(f" {html_file} ({len(blocks)} Python blocks)")
112+
print(f"{'='*60}")
113+
114+
for i, (label, code) in enumerate(blocks):
115+
total += 1
116+
success, output, elapsed_ms = run_python_block(
117+
code, label, html_file
118+
)
119+
120+
if success:
121+
passed += 1
122+
print(f" ✅ [{i+1}] {label} ({elapsed_ms:.1f}ms)")
123+
else:
124+
failed += 1
125+
fail_msg = f" ❌ [{i+1}] {label} in {html_file}"
126+
print(fail_msg)
127+
print(f" {output[:200]}")
128+
failures.append(f"{html_file} [{i+1}] {label}")
129+
130+
print(f"\n{'='*60}")
131+
print(f" Results: {passed}/{total} passed, {failed} failed")
132+
print(f"{'='*60}")
133+
134+
if failures:
135+
print("\nFailed examples:")
136+
for f in failures:
137+
print(f" - {f}")
138+
return 1
139+
140+
print("\n✅ All Python examples validated successfully!")
141+
return 0
142+
143+
144+
if __name__ == "__main__":
145+
sys.exit(main())

0 commit comments

Comments
 (0)