suprimedev commited on
Commit
4addbf0
·
verified ·
1 Parent(s): 00ea332

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +57 -111
app.py CHANGED
@@ -3,11 +3,13 @@ import sys
3
  import time
4
  import ast
5
  import traceback
 
 
6
  from multiprocessing import Process, Queue
7
 
8
  try:
9
- import resource # Not available on Windows, but Spaces run Linux.
10
- except Exception: # pragma: no cover
11
  resource = None
12
 
13
  import gradio as gr
@@ -16,51 +18,15 @@ import gradio as gr
16
  # Sandbox utilities
17
  # -----------------------------
18
  SAFE_BUILTINS = {
19
- "abs": abs,
20
- "all": all,
21
- "any": any,
22
- "bool": bool,
23
- "bytes": bytes,
24
- "callable": callable,
25
- "chr": chr,
26
- "complex": complex,
27
- "dict": dict,
28
- "dir": dir,
29
- "divmod": divmod,
30
- "enumerate": enumerate,
31
- "filter": filter,
32
- "float": float,
33
- "format": format,
34
- "hash": hash,
35
- "help": help,
36
- "hex": hex,
37
- "int": int,
38
- "isinstance": isinstance,
39
- "issubclass": issubclass,
40
- "iter": iter,
41
- "len": len,
42
- "list": list,
43
- "map": map,
44
- "max": max,
45
- "min": min,
46
- "next": next,
47
- "object": object,
48
- "oct": oct,
49
- "ord": ord,
50
- "pow": pow,
51
- "print": print,
52
- "range": range,
53
- "repr": repr,
54
- "reversed": reversed,
55
- "round": round,
56
- "set": set,
57
- "slice": slice,
58
- "sorted": sorted,
59
- "str": str,
60
- "sum": sum,
61
- "tuple": tuple,
62
- "type": type,
63
- "zip": zip,
64
  }
65
 
66
  DANGEROUS_NAMES = {
@@ -68,76 +34,64 @@ DANGEROUS_NAMES = {
68
  "globals", "locals", "vars", "exit", "quit",
69
  }
70
 
71
- DANGEROUS_MODULE_TOKENS = {
72
- "os", "sys", "subprocess", "socket", "shutil", "ctypes", "signal", "resource",
73
- }
74
-
75
 
76
- def _lint_code_for_danger(code: str, allow_imports: bool) -> None:
77
- """Lightweight static checks; raises ValueError on disallowed constructs."""
78
  try:
79
  tree = ast.parse(code, mode="exec")
80
  except SyntaxError as e:
81
  raise ValueError(f"SyntaxError: {e}")
82
 
 
83
  for node in ast.walk(tree):
84
- # Disallow Attribute or Name usage of dunder or dangerous names
85
- if isinstance(node, ast.Name):
86
- if node.id in DANGEROUS_NAMES:
87
- raise ValueError(f"Use of `{node.id}` is not allowed in this sandbox.")
88
- if not allow_imports and node.id in DANGEROUS_MODULE_TOKENS:
89
- raise ValueError(
90
- f"Importing/using `{node.id}` is not allowed in this sandbox. Enable 'Allow imports' to proceed (unsafe)."
91
- )
92
- if isinstance(node, ast.Attribute):
93
- if node.attr.startswith("__"):
94
- raise ValueError("Access to dunder attributes is blocked in this sandbox.")
95
- if isinstance(node, ast.Import) or isinstance(node, ast.ImportFrom):
96
- if not allow_imports:
97
- raise ValueError("Imports are disabled. Tick 'Allow imports (unsafe)' to enable.")
98
-
99
-
100
- def _restricted_import(*args, **kwargs):
101
- raise ImportError("Importing modules is disabled in this sandbox.")
102
 
103
 
104
  def _apply_limits(mem_mb: int, cpu_seconds: int):
105
  if resource is None:
106
  return
107
- # Limit address space (virtual memory)
108
  if mem_mb and mem_mb > 0:
109
  bytes_limit = int(mem_mb) * 1024 * 1024
110
  resource.setrlimit(resource.RLIMIT_AS, (bytes_limit, bytes_limit))
111
- # Limit CPU seconds
112
  if cpu_seconds and cpu_seconds > 0:
113
  resource.setrlimit(resource.RLIMIT_CPU, (cpu_seconds, cpu_seconds))
114
 
115
 
116
- def _worker(code: str, stdin_data: str, allow_imports: bool, mem_limit_mb: int, cpu_limit_s: int, q: Queue):
117
  stdout_buf, stderr_buf = io.StringIO(), io.StringIO()
118
  start = time.time()
119
  try:
120
  _apply_limits(mem_limit_mb, cpu_limit_s)
121
 
122
- # Prepare I/O redirection
123
  orig_stdout, orig_stderr, orig_stdin = sys.stdout, sys.stderr, sys.stdin
124
  sys.stdout, sys.stderr, sys.stdin = stdout_buf, stderr_buf, io.StringIO(stdin_data or "")
125
 
126
- # Lint for dangerous constructs
127
- _lint_code_for_danger(code, allow_imports)
 
 
128
 
129
- # Prepare restricted globals
130
  safe_globals = {"__builtins__": SAFE_BUILTINS.copy()}
131
- if not allow_imports:
132
- safe_globals["__builtins__"]["__import__"] = _restricted_import
133
 
134
- # Execute user code
135
  exec(compile(code, filename="<user_code>", mode="exec"), safe_globals, None)
136
 
137
  except Exception:
138
  traceback.print_exc(file=stderr_buf)
139
  finally:
140
- # Restore std streams
141
  try:
142
  sys.stdout, sys.stderr, sys.stdin = orig_stdout, orig_stderr, orig_stdin
143
  except Exception:
@@ -150,20 +104,17 @@ def _worker(code: str, stdin_data: str, allow_imports: bool, mem_limit_mb: int,
150
  })
151
 
152
 
153
- def run_code(code: str, stdin_data: str = "", timeout_s: float = 3.0, allow_imports: bool = False, mem_limit_mb: int = 256):
154
- """Execute code in a separate process with time & memory limits."""
155
  q: Queue = Queue()
156
- p = Process(target=_worker, args=(code, stdin_data, allow_imports, int(mem_limit_mb), int(max(1, timeout_s)), q))
157
  p.start()
158
  p.join(timeout=timeout_s)
159
 
160
  if p.is_alive():
161
  p.terminate()
162
- try:
163
- p.join(1)
164
- except Exception:
165
- pass
166
- return "", "Execution timed out.", timeout_s
167
 
168
  try:
169
  result = q.get_nowait()
@@ -177,29 +128,25 @@ def run_code(code: str, stdin_data: str = "", timeout_s: float = 3.0, allow_impo
177
  # Gradio UI
178
  # -----------------------------
179
  EXAMPLE_CODE = """
180
- # Example: Fibonacci numbers under 200
181
- fib = [0, 1]
182
- while fib[-1] + fib[-2] < 200:
183
- fib.append(fib[-1] + fib[-2])
184
- print("Fibonacci:", fib)
185
  """.strip()
186
 
187
 
188
- def interface_run(code, stdin_text, timeout_s, allow_imports, mem_limit):
189
- stdout, stderr, elapsed = run_code(code or "", stdin_text or "", timeout_s or 3.0, allow_imports, mem_limit or 256)
190
  status = f"Finished in {elapsed:.3f}s"
191
  return stdout, stderr, status
192
 
193
 
194
- with gr.Blocks(title="Python Code Runner (Sandbox)") as demo:
195
  gr.Markdown("""
196
- # 🐍 Python Code Runner (Sandbox)
197
- A lightweight sandbox to test Python snippets inside your Hugging Face Space.
198
-
199
- **Notes**
200
- - Imports are **disabled by default** for safety. You can enable them (unsafe) via the checkbox.
201
- - CPU and memory limits help protect the Space from heavy or infinite computations.
202
- - `input()` is disabled; use the *stdin* box instead.
203
  """)
204
 
205
  with gr.Row():
@@ -207,9 +154,8 @@ with gr.Blocks(title="Python Code Runner (Sandbox)") as demo:
207
  with gr.Row():
208
  stdin_tb = gr.Textbox(lines=3, label="stdin (optional)")
209
  with gr.Row():
210
- timeout = gr.Slider(1, 20, value=3, step=1, label="Time limit (seconds)")
211
- mem_limit = gr.Slider(64, 2048, value=256, step=64, label="Memory limit (MB)")
212
- allow_imports = gr.Checkbox(False, label="Allow imports (unsafe)")
213
  run_btn = gr.Button("Run", variant="primary")
214
 
215
  with gr.Row():
@@ -218,13 +164,13 @@ with gr.Blocks(title="Python Code Runner (Sandbox)") as demo:
218
  stderr = gr.Textbox(lines=8, label="stderr", interactive=False)
219
  status = gr.Label(value="Idle", label="Status")
220
 
221
- run_btn.click(interface_run, inputs=[code, stdin_tb, timeout, allow_imports, mem_limit], outputs=[stdout, stderr, status])
222
 
223
  gr.Examples(
224
  examples=[
225
  ["print('Hello from Space!')"],
226
- ["for i in range(5):\n print(i*i)"] ,
227
- ["# Using stdin:\nname = input('Name: ')\nprint('Hi', name)"]
228
  ],
229
  inputs=[code],
230
  label="Quick examples"
 
3
  import time
4
  import ast
5
  import traceback
6
+ import importlib
7
+ import subprocess
8
  from multiprocessing import Process, Queue
9
 
10
  try:
11
+ import resource # Linux only (Hugging Face Spaces)
12
+ except Exception:
13
  resource = None
14
 
15
  import gradio as gr
 
18
  # Sandbox utilities
19
  # -----------------------------
20
  SAFE_BUILTINS = {
21
+ "abs": abs, "all": all, "any": any, "bool": bool, "bytes": bytes, "callable": callable,
22
+ "chr": chr, "complex": complex, "dict": dict, "dir": dir, "divmod": divmod,
23
+ "enumerate": enumerate, "filter": filter, "float": float, "format": format,
24
+ "hash": hash, "help": help, "hex": hex, "int": int, "isinstance": isinstance,
25
+ "issubclass": issubclass, "iter": iter, "len": len, "list": list, "map": map,
26
+ "max": max, "min": min, "next": next, "object": object, "oct": oct, "ord": ord,
27
+ "pow": pow, "print": print, "range": range, "repr": repr, "reversed": reversed,
28
+ "round": round, "set": set, "slice": slice, "sorted": sorted, "str": str,
29
+ "sum": sum, "tuple": tuple, "type": type, "zip": zip,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  }
31
 
32
  DANGEROUS_NAMES = {
 
34
  "globals", "locals", "vars", "exit", "quit",
35
  }
36
 
 
 
 
 
37
 
38
+ def _parse_imports(code: str) -> list:
39
+ """Parse AST and return list of imports found in code."""
40
  try:
41
  tree = ast.parse(code, mode="exec")
42
  except SyntaxError as e:
43
  raise ValueError(f"SyntaxError: {e}")
44
 
45
+ imports = []
46
  for node in ast.walk(tree):
47
+ if isinstance(node, (ast.Import, ast.ImportFrom)):
48
+ for alias in node.names:
49
+ pkg = alias.name.split(".")[0]
50
+ imports.append(pkg)
51
+ return list(set(imports))
52
+
53
+
54
+ def _install_missing(packages):
55
+ """Install packages if not already installed."""
56
+ for pkg in packages:
57
+ try:
58
+ importlib.import_module(pkg)
59
+ except ImportError:
60
+ subprocess.check_call([sys.executable, "-m", "pip", "install", pkg])
 
 
 
 
61
 
62
 
63
  def _apply_limits(mem_mb: int, cpu_seconds: int):
64
  if resource is None:
65
  return
 
66
  if mem_mb and mem_mb > 0:
67
  bytes_limit = int(mem_mb) * 1024 * 1024
68
  resource.setrlimit(resource.RLIMIT_AS, (bytes_limit, bytes_limit))
 
69
  if cpu_seconds and cpu_seconds > 0:
70
  resource.setrlimit(resource.RLIMIT_CPU, (cpu_seconds, cpu_seconds))
71
 
72
 
73
+ def _worker(code: str, stdin_data: str, mem_limit_mb: int, cpu_limit_s: int, q: Queue):
74
  stdout_buf, stderr_buf = io.StringIO(), io.StringIO()
75
  start = time.time()
76
  try:
77
  _apply_limits(mem_limit_mb, cpu_limit_s)
78
 
79
+ # Redirect streams
80
  orig_stdout, orig_stderr, orig_stdin = sys.stdout, sys.stderr, sys.stdin
81
  sys.stdout, sys.stderr, sys.stdin = stdout_buf, stderr_buf, io.StringIO(stdin_data or "")
82
 
83
+ # Check imports and install missing ones
84
+ imports = _parse_imports(code)
85
+ if imports:
86
+ _install_missing(imports)
87
 
 
88
  safe_globals = {"__builtins__": SAFE_BUILTINS.copy()}
 
 
89
 
 
90
  exec(compile(code, filename="<user_code>", mode="exec"), safe_globals, None)
91
 
92
  except Exception:
93
  traceback.print_exc(file=stderr_buf)
94
  finally:
 
95
  try:
96
  sys.stdout, sys.stderr, sys.stdin = orig_stdout, orig_stderr, orig_stdin
97
  except Exception:
 
104
  })
105
 
106
 
107
+ def run_code(code: str, stdin_data: str = "", timeout_s: float = 5.0, mem_limit_mb: int = 512):
 
108
  q: Queue = Queue()
109
+ p = Process(target=_worker, args=(code, stdin_data, int(mem_limit_mb), int(max(1, timeout_s)), q))
110
  p.start()
111
  p.join(timeout=timeout_s)
112
 
113
  if p.is_alive():
114
  p.terminate()
115
+ try: p.join(1)
116
+ except Exception: pass
117
+ return "", "Execution timed out.", timeout_s
 
 
118
 
119
  try:
120
  result = q.get_nowait()
 
128
  # Gradio UI
129
  # -----------------------------
130
  EXAMPLE_CODE = """
131
+ import requests
132
+ r = requests.get("https://httpbin.org/get")
133
+ print("Status:", r.status_code)
134
+ print("JSON:", r.json())
 
135
  """.strip()
136
 
137
 
138
+ def interface_run(code, stdin_text, timeout_s, mem_limit):
139
+ stdout, stderr, elapsed = run_code(code or "", stdin_text or "", timeout_s or 5.0, mem_limit or 512)
140
  status = f"Finished in {elapsed:.3f}s"
141
  return stdout, stderr, status
142
 
143
 
144
+ with gr.Blocks(title="Python Code Runner (Free Sandbox)") as demo:
145
  gr.Markdown("""
146
+ # 🐍 Python Code Runner (Free Sandbox)
147
+ اجرای کد پایتون با نصب خودکار هر کتابخانه‌ای که import کنید. ⚠️
148
+
149
+ > توجه: این نسخه ناامن است و می‌تواند ریسک امنیتی داشته باشد.
 
 
 
150
  """)
151
 
152
  with gr.Row():
 
154
  with gr.Row():
155
  stdin_tb = gr.Textbox(lines=3, label="stdin (optional)")
156
  with gr.Row():
157
+ timeout = gr.Slider(1, 30, value=5, step=1, label="Time limit (seconds)")
158
+ mem_limit = gr.Slider(64, 4096, value=512, step=64, label="Memory limit (MB)")
 
159
  run_btn = gr.Button("Run", variant="primary")
160
 
161
  with gr.Row():
 
164
  stderr = gr.Textbox(lines=8, label="stderr", interactive=False)
165
  status = gr.Label(value="Idle", label="Status")
166
 
167
+ run_btn.click(interface_run, inputs=[code, stdin_tb, timeout, mem_limit], outputs=[stdout, stderr, status])
168
 
169
  gr.Examples(
170
  examples=[
171
  ["print('Hello from Space!')"],
172
+ ["import numpy as np\nprint(np.arange(10))"],
173
+ ["import requests\nprint(requests.get('https://httpbin.org/get').status_code)"],
174
  ],
175
  inputs=[code],
176
  label="Quick examples"