YugJ commited on
Commit
25d9f62
·
verified ·
1 Parent(s): 95db8cd

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +508 -0
app.py ADDED
@@ -0,0 +1,508 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Python to Rust / C++ performance translator.
2
+
3
+ An LLM ports a Python program to a compiled language; both are then executed
4
+ under the same sandbox so the speedup and - just as importantly - the
5
+ correctness of the translation can be measured rather than assumed.
6
+
7
+ Run locally: python app.py
8
+ Deployed: see Dockerfile (Hugging Face Spaces, Docker SDK)
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import html
14
+ import os
15
+ import re
16
+ import uuid
17
+
18
+ import gradio as gr
19
+ from dotenv import load_dotenv
20
+
21
+ # override=False so real environment variables win over a stray .env file.
22
+ # On a host, secrets arrive as env vars and must not be shadowed by a file.
23
+ load_dotenv(override=False)
24
+
25
+ # Hugging Face Spaces sets SPACE_ID on every Space. Detecting it beats relying
26
+ # on a manually-set variable: the Gradio SDK has no Dockerfile to carry ENV, so
27
+ # a forgotten setting would silently run the public demo in local mode -
28
+ # frontier models ungated against the owner's key, no rate limit, and the
29
+ # on-disk-secret check disabled. This must happen before providers/sandbox are
30
+ # imported, since both read the flag at import time.
31
+ ON_SPACES = bool(os.getenv("SPACE_ID"))
32
+ if ON_SPACES:
33
+ os.environ["PUBLIC_DEPLOYMENT"] = "1"
34
+
35
+ import providers # noqa: E402 - imported after load_dotenv so it sees the keys
36
+ import sandbox # noqa: E402
37
+ from examples import DEFAULT_EXAMPLE, EXAMPLES # noqa: E402
38
+ from styles import CSS # noqa: E402
39
+ from system_info import retrieve_system_info # noqa: E402
40
+
41
+ sandbox.assert_safe_to_deploy()
42
+
43
+ REPO_URL = "https://github.com/Yugjohri/python-to-rust-cpp"
44
+
45
+ SYSTEM_INFO = retrieve_system_info()
46
+ TOOLCHAIN = sandbox.toolchain_status()
47
+ COMPILABLE = sandbox.available_languages()
48
+
49
+
50
+ # --------------------------------------------------------------------------
51
+ # prompting
52
+ # --------------------------------------------------------------------------
53
+
54
+
55
+ def build_system_prompt(language_key: str) -> str:
56
+ lang = sandbox.LANGUAGES[language_key]
57
+ integer_note = (
58
+ "Rust integers are fixed width and overflow panics in debug but wraps in "
59
+ "release - pick widths (i64/i128/u64) that cannot overflow for these inputs."
60
+ if language_key == "rust" else
61
+ "C++ integers are fixed width and signed overflow is undefined behaviour - "
62
+ "pick widths (long long / __int128) that cannot overflow for these inputs."
63
+ )
64
+ return f"""You convert Python programs into high-performance {lang.display}.
65
+
66
+ Rules:
67
+ - Respond with {lang.display} source only. No prose, no markdown fences.
68
+ - A single self-contained file using only the standard library.
69
+ - Output must be byte-for-byte identical to the Python program's, including
70
+ number formatting and wording. Format floats with the same precision.
71
+ - Preserve the algorithm. Do not substitute an asymptotically better one - the
72
+ point is to measure what the same work costs in a compiled language.
73
+ - {integer_note}
74
+ - Python integers are arbitrary precision; account for that where it matters.
75
+ - Time the computation the same way the Python does and print it identically.
76
+ """
77
+
78
+
79
+ def build_user_prompt(python_code: str, language_key: str) -> str:
80
+ lang = sandbox.LANGUAGES[language_key]
81
+ cpu = SYSTEM_INFO.get("cpu", {})
82
+ compiler = lang.find_compiler() or lang.compilers[0]
83
+ flags = " ".join(
84
+ sandbox.build_compile_command(lang, "CC", f"main.{lang.extension}", "program")[1:]
85
+ )
86
+ # The exact toolchain version matters. Without it models reach for
87
+ # extensions like unsigned __int128, which GCC supports on 64-bit Linux
88
+ # but MinGW does not - a compile failure the model could have avoided.
89
+ version = TOOLCHAIN.get(language_key, "") or "version unknown"
90
+ os_name = SYSTEM_INFO.get("os", {}).get("system", "")
91
+
92
+ constraints = ""
93
+ if language_key == "cpp":
94
+ if sandbox.cpp_has_int128():
95
+ constraints = "- `__int128` IS available here if you need a wider accumulator.\n"
96
+ else:
97
+ constraints = (
98
+ "- `__int128` / `unsigned __int128` are NOT available with this "
99
+ "compiler. Using them will fail to compile. Use `long long` / "
100
+ "`unsigned long long`, or restructure to avoid needing 128 bits.\n"
101
+ )
102
+
103
+ return f"""Port this Python program to {lang.display}.
104
+
105
+ Target machine: {cpu.get('brand', 'unknown CPU')}, {cpu.get('cores_logical', '?')} logical cores, {os_name}.
106
+ Compiler present: {version}
107
+ It will be compiled with exactly:
108
+ {os.path.basename(compiler)} {flags}
109
+
110
+ Hard constraints for this toolchain:
111
+ {constraints}- Use only what that compiler version supports on this platform.
112
+
113
+ Respond with {lang.display} source only.
114
+
115
+ ```python
116
+ {python_code}
117
+ ```
118
+ """
119
+
120
+
121
+ # --------------------------------------------------------------------------
122
+ # helpers
123
+ # --------------------------------------------------------------------------
124
+
125
+ FENCE = re.compile(r"^\s*```[a-zA-Z+]*\s*\n(.*?)\n?\s*```\s*$", re.DOTALL)
126
+ TIME_LINE = re.compile(r"^\s*execution time:.*$", re.IGNORECASE | re.MULTILINE)
127
+
128
+
129
+ def strip_fences(text: str) -> str:
130
+ """Remove a surrounding markdown code fence if the model added one."""
131
+ match = FENCE.match(text.strip())
132
+ if match:
133
+ return match.group(1)
134
+ for token in ("```rust", "```rs", "```cpp", "```c++", "```"):
135
+ text = text.replace(token, "")
136
+ return text.strip()
137
+
138
+
139
+ def extract_seconds(output: str) -> float | None:
140
+ """Pull the self-reported execution time out of a program's output."""
141
+ match = re.search(r"execution time:\s*([0-9.]+)", output or "", re.IGNORECASE)
142
+ if match:
143
+ try:
144
+ return float(match.group(1))
145
+ except ValueError:
146
+ return None
147
+ return None
148
+
149
+
150
+ def comparable(output: str) -> str:
151
+ """Output with the timing line removed, for correctness comparison.
152
+
153
+ Timings legitimately differ between runs; everything else must not.
154
+ """
155
+ return TIME_LINE.sub("", output or "").strip()
156
+
157
+
158
+ def format_duration(seconds: float) -> tuple[str, str]:
159
+ """Split the magnitude from the unit.
160
+
161
+ Returned separately so the UI can right-align the digits in their own
162
+ column and left-align the unit in another. Formatting them as one string
163
+ and right-aligning that makes the decimal points jitter between rows,
164
+ because "s" and "ms" are different widths.
165
+ """
166
+ if seconds >= 1:
167
+ return f"{seconds:.3f}", "s"
168
+ if seconds >= 1e-3:
169
+ return f"{seconds * 1e3:.2f}", "ms"
170
+ return f"{seconds * 1e6:.0f}", "us"
171
+
172
+
173
+ def idle_verdict(language_key: str = sandbox.DEFAULT_LANGUAGE) -> str:
174
+ lang = sandbox.LANGUAGES[language_key]
175
+ return (
176
+ f'<div class="verdict"><p class="idle">Run the Python, port it, then run '
177
+ f"the {lang.display} - the speedup and a correctness check appear here.</p></div>"
178
+ )
179
+
180
+
181
+ def render_verdict(py_out: str, target_out: str, language_key: str) -> str:
182
+ """Build the hero panel: speedup, timing bars, correctness badge."""
183
+ lang = sandbox.LANGUAGES[language_key]
184
+ py_seconds = extract_seconds(py_out)
185
+ target_seconds = extract_seconds(target_out)
186
+
187
+ if py_seconds is None or target_seconds is None or target_seconds <= 0:
188
+ return idle_verdict(language_key)
189
+
190
+ ratio = py_seconds / target_seconds
191
+ speedup = f"{ratio:,.0f}x" if ratio >= 10 else f"{ratio:.1f}x"
192
+
193
+ widest = max(py_seconds, target_seconds)
194
+ py_width = max(2.0, py_seconds / widest * 100)
195
+ target_width = max(2.0, target_seconds / widest * 100)
196
+
197
+ py_body, target_body = comparable(py_out), comparable(target_out)
198
+ if not py_body or not target_body:
199
+ badge = '<span class="badge unknown">Run both to verify output</span>'
200
+ elif py_body == target_body:
201
+ badge = '<span class="badge match">Outputs identical</span>'
202
+ else:
203
+ badge = '<span class="badge mismatch">Outputs differ - translation is wrong</span>'
204
+
205
+ py_num, py_unit = format_duration(py_seconds)
206
+ tg_num, tg_unit = format_duration(target_seconds)
207
+
208
+ return f"""
209
+ <div class="verdict">
210
+ <div class="verdict-grid">
211
+ <div class="speedup-cell">
212
+ <div class="speedup">{speedup}</div>
213
+ <div class="speedup-label">faster in {html.escape(lang.display)}</div>
214
+ </div>
215
+ <div class="timings">
216
+ <div class="timing-row py">
217
+ <span class="name">Python</span>
218
+ <span class="bar-track"><span class="bar" style="width:{py_width:.1f}%"></span></span>
219
+ <span class="num">{py_num}</span><span class="unit">{py_unit}</span>
220
+ </div>
221
+ <div class="timing-row target">
222
+ <span class="name">{html.escape(lang.display)}</span>
223
+ <span class="bar-track"><span class="bar" style="width:{target_width:.1f}%"></span></span>
224
+ <span class="num">{tg_num}</span><span class="unit">{tg_unit}</span>
225
+ </div>
226
+ </div>
227
+ <div class="badge-cell">{badge}</div>
228
+ </div>
229
+ </div>
230
+ """
231
+
232
+
233
+ # --------------------------------------------------------------------------
234
+ # event handlers
235
+ # --------------------------------------------------------------------------
236
+
237
+
238
+ def on_run_python(code: str, target_out: str, language_key: str):
239
+ result = sandbox.run_python(code)
240
+ text = result.display
241
+ return text, render_verdict(text, target_out, language_key)
242
+
243
+
244
+ def on_run_target(code: str, py_out: str, language_key: str):
245
+ result, _ = sandbox.run_compiled(language_key, code)
246
+ text = result.display
247
+ return text, render_verdict(py_out, text, language_key)
248
+
249
+
250
+ def on_port(model_id: str, python_code: str, language_key: str,
251
+ user_key: str, session_id: str):
252
+ """Translate the Python using the selected model."""
253
+ lang = sandbox.LANGUAGES[language_key]
254
+ if not python_code.strip():
255
+ return "// Nothing to port - paste some Python on the left first.", ""
256
+
257
+ user_key = (user_key or "").strip()
258
+
259
+ # Only the host's own key is rate limited; a visitor's key costs us nothing.
260
+ if not user_key and providers.PUBLIC:
261
+ allowed, message = providers.limiter.check(session_id)
262
+ if not allowed:
263
+ return f"// {message}", _quota_html(session_id)
264
+
265
+ try:
266
+ client, used_host_key = providers.build_client(model_id, user_key)
267
+ except providers.NoKeyError as e:
268
+ return f"// {e}", _quota_html(session_id)
269
+
270
+ model = providers.BY_ID[model_id]
271
+ kwargs = {"reasoning_effort": "high"} if model.reasoning else {}
272
+
273
+ try:
274
+ response = client.chat.completions.create(
275
+ model=model.id,
276
+ messages=[
277
+ {"role": "system", "content": build_system_prompt(language_key)},
278
+ {"role": "user", "content": build_user_prompt(python_code, language_key)},
279
+ ],
280
+ **kwargs,
281
+ )
282
+ except Exception as e:
283
+ # scrub() because provider SDKs sometimes echo the key back in errors
284
+ detail = providers.scrub(f"{type(e).__name__}: {e}")
285
+ return (f"// Request to {model.display} failed.\n// {detail}",
286
+ _quota_html(session_id))
287
+
288
+ if used_host_key and providers.PUBLIC:
289
+ providers.limiter.record(session_id)
290
+
291
+ reply = response.choices[0].message.content or ""
292
+ return strip_fences(reply), _quota_html(session_id)
293
+
294
+
295
+ def on_change_language(language_key: str):
296
+ """Relabel everything that names the target language, and clear stale output."""
297
+ lang = sandbox.LANGUAGES[language_key]
298
+ ready = language_key in COMPILABLE
299
+ return (
300
+ gr.update(label=f"{lang.display} translation", language=lang.highlight, value=""),
301
+ gr.update(label=f"{lang.display} output", value=""),
302
+ gr.update(value=f"Port to {lang.display}"),
303
+ gr.update(
304
+ value=f"Run {lang.display}" if ready else f"Run {lang.display} (no compiler)",
305
+ interactive=ready,
306
+ ),
307
+ idle_verdict(language_key),
308
+ )
309
+
310
+
311
+ def on_pick_example(name: str):
312
+ return EXAMPLES.get(name, "")
313
+
314
+
315
+ def _quota_html(session_id: str) -> str:
316
+ if not providers.PUBLIC or providers.limiter.limit <= 0:
317
+ return ""
318
+ left = providers.limiter.remaining(session_id)
319
+ return (
320
+ f'<p class="key-note">Free demo conversions left this hour: '
321
+ f"<strong>{left}</strong>. Use your own key below for unlimited access.</p>"
322
+ )
323
+
324
+
325
+ def new_session() -> str:
326
+ return uuid.uuid4().hex
327
+
328
+
329
+ # --------------------------------------------------------------------------
330
+ # static chrome
331
+ # --------------------------------------------------------------------------
332
+
333
+ START_LANG = sandbox.DEFAULT_LANGUAGE if sandbox.DEFAULT_LANGUAGE in COMPILABLE \
334
+ else (COMPILABLE[0] if COMPILABLE else sandbox.DEFAULT_LANGUAGE)
335
+ START = sandbox.LANGUAGES[START_LANG]
336
+
337
+ LANGUAGE_CHOICES = [
338
+ (f"{lang.display}" if key in COMPILABLE else f"{lang.display} (no compiler here)", key)
339
+ for key, lang in sandbox.LANGUAGES.items()
340
+ ]
341
+
342
+ MASTHEAD = """
343
+ <div class="masthead">
344
+ <h1><span class="py">Python</span><span class="arrow">&#8594;</span><span class="rs">Rust &amp; C++</span></h1>
345
+ <p class="tagline">LLM translation, measured and verified</p>
346
+ <p>An LLM ports a Python program to Rust or C++. Both are compiled and executed
347
+ in a sandbox on this machine, so the speedup is measured rather than
348
+ estimated &mdash; and the outputs are compared, because a fast translation
349
+ that changes the answer is a broken one.</p>
350
+ <div class="links">
351
+ <a href="%s" target="_blank" rel="noopener">Source on GitHub</a>
352
+ <a href="#how">How it works</a>
353
+ </div>
354
+ </div>
355
+ """ % REPO_URL
356
+
357
+
358
+ def build_footer() -> str:
359
+ installed = [f"{sandbox.LANGUAGES[k].display}: {html.escape(TOOLCHAIN[k])}"
360
+ for k in COMPILABLE]
361
+ missing = [sandbox.LANGUAGES[k].display for k in sandbox.LANGUAGES
362
+ if k not in COMPILABLE]
363
+ missing_note = (
364
+ f"<br><strong>Note:</strong> no compiler found for {', '.join(missing)}, "
365
+ f"so translations to it can be generated but not executed here."
366
+ if missing else ""
367
+ )
368
+ return f"""
369
+ <div class="footer" id="how">
370
+ <p><strong>How it works.</strong> Your Python goes to the selected model with a
371
+ prompt that pins the exact compile flags and forbids algorithmic substitution.
372
+ The returned source is compiled with full optimisation and run. Both programs
373
+ self-report their execution time, and the non-timing output of each is compared
374
+ to verify the translation actually preserved the answer.</p>
375
+ <p><strong>Sandboxing.</strong> Submitted code never runs in the web server's
376
+ process. Each run gets a fresh subprocess with an allowlisted environment
377
+ containing no API keys, a scrubbed working directory, a wall-clock timeout, and
378
+ &mdash; on Linux &mdash; CPU, memory, and process-count limits.
379
+ Isolation here: {html.escape(TOOLCHAIN.get('isolation', 'unknown'))}.</p>
380
+ <p><strong>Keys.</strong> Frontier models never run on this instance's key. A key
381
+ you paste is used for that single request and is never logged, stored, or written
382
+ to disk.{missing_note}</p>
383
+ <p>{html.escape(TOOLCHAIN.get('python', ''))}{(' &middot; ' + ' &middot; '.join(installed)) if installed else ''}</p>
384
+ </div>
385
+ """
386
+
387
+
388
+ # --------------------------------------------------------------------------
389
+ # interface
390
+ # --------------------------------------------------------------------------
391
+
392
+
393
+ def build_ui() -> gr.Blocks:
394
+ with gr.Blocks(title="Python → Rust & C++",
395
+ analytics_enabled=False) as ui:
396
+ session = gr.State(value=new_session)
397
+
398
+ gr.HTML(MASTHEAD)
399
+ verdict = gr.HTML(idle_verdict(START_LANG))
400
+
401
+ with gr.Row(equal_height=True, elem_classes=["picker"]):
402
+ example = gr.Dropdown(
403
+ choices=list(EXAMPLES.keys()), value=DEFAULT_EXAMPLE,
404
+ label="Example program", scale=5,
405
+ )
406
+ language = gr.Dropdown(
407
+ choices=LANGUAGE_CHOICES, value=START_LANG,
408
+ label="Translate to", scale=3,
409
+ )
410
+ model = gr.Dropdown(
411
+ choices=providers.dropdown_choices(), value=providers.default_model(),
412
+ label="Model", scale=5,
413
+ )
414
+
415
+ gr.Markdown(providers.availability_summary(), elem_classes=["status-line"])
416
+
417
+ with gr.Row(equal_height=True):
418
+ with gr.Column(scale=6):
419
+ python_box = gr.Code(
420
+ label="Python source", value=EXAMPLES[DEFAULT_EXAMPLE],
421
+ language="python", lines=24, elem_classes=["pane"],
422
+ )
423
+ with gr.Column(scale=6):
424
+ target_box = gr.Code(
425
+ label=f"{START.display} translation", value="",
426
+ language=START.highlight, lines=24, elem_classes=["pane"],
427
+ )
428
+
429
+ with gr.Row(elem_classes=["controls"]):
430
+ run_py = gr.Button("Run Python", elem_classes=["btn-run", "py"])
431
+ convert = gr.Button(f"Port to {START.display}", variant="primary",
432
+ elem_classes=["btn-convert"])
433
+ run_target = gr.Button(
434
+ f"Run {START.display}" if START_LANG in COMPILABLE
435
+ else f"Run {START.display} (no compiler)",
436
+ interactive=START_LANG in COMPILABLE,
437
+ elem_classes=["btn-run", "rust"],
438
+ )
439
+
440
+ with gr.Row(equal_height=True):
441
+ with gr.Column(scale=6):
442
+ py_out = gr.Textbox(label="Python output", lines=8,
443
+ elem_classes=["out-box", "py-out"],
444
+ buttons=["copy"])
445
+ with gr.Column(scale=6):
446
+ target_out = gr.Textbox(label=f"{START.display} output", lines=8,
447
+ elem_classes=["out-box", "rust-out"],
448
+ buttons=["copy"])
449
+
450
+ quota = gr.HTML(_quota_html(""))
451
+
452
+ with gr.Accordion("Use your own API key (optional)", open=False):
453
+ gr.Markdown(
454
+ "Needed for the frontier models marked **(need your own key)**, "
455
+ "which never run on this instance's account. Also removes the "
456
+ "rate limit.\n\n"
457
+ "**Worth knowing first:** on this benchmark GPT-5 Nano scored 62x "
458
+ "and GPT-5 scored 63x. The unmarked models need no key and give "
459
+ "you effectively the same answer.\n\n"
460
+ "A key you paste is sent over HTTPS, used for that single request, "
461
+ "then discarded - never logged, stored, or written to disk. It is "
462
+ "not saved in your browser either, so it clears on reload.\n\n"
463
+ "The code is open source if you would rather "
464
+ f"[read it]({REPO_URL}/blob/main/providers.py) or run this locally.",
465
+ elem_classes=["key-note"],
466
+ )
467
+ user_key = gr.Textbox(label="API key", placeholder="sk-...",
468
+ type="password", elem_classes=["keybox"])
469
+
470
+ gr.HTML(build_footer())
471
+
472
+ # ---- wiring ----
473
+ example.change(fn=on_pick_example, inputs=[example], outputs=[python_box])
474
+
475
+ language.change(
476
+ fn=on_change_language,
477
+ inputs=[language],
478
+ outputs=[target_box, target_out, convert, run_target, verdict],
479
+ )
480
+
481
+ run_py.click(
482
+ fn=on_run_python,
483
+ inputs=[python_box, target_out, language],
484
+ outputs=[py_out, verdict],
485
+ )
486
+ convert.click(
487
+ fn=on_port,
488
+ inputs=[model, python_box, language, user_key, session],
489
+ outputs=[target_box, quota],
490
+ )
491
+ run_target.click(
492
+ fn=on_run_target,
493
+ inputs=[target_box, py_out, language],
494
+ outputs=[target_out, verdict],
495
+ )
496
+
497
+ return ui
498
+
499
+
500
+ if __name__ == "__main__":
501
+ build_ui().launch(
502
+ css=CSS,
503
+ theme=gr.themes.Base(),
504
+ # Spaces route traffic to the container, so it must bind all interfaces.
505
+ # Locally, stay on loopback so the dev server is not exposed to the LAN.
506
+ server_name=os.getenv("SERVER_NAME", "0.0.0.0" if ON_SPACES else "127.0.0.1"),
507
+ server_port=int(os.getenv("PORT", "7860")),
508
+ )