Merge branch 'main' into pip
This commit is contained in:
commit
c436f3ca67
6 changed files with 61 additions and 9 deletions
|
|
@ -2545,14 +2545,24 @@ def _python_exec(
|
|||
pass
|
||||
try:
|
||||
fd, tmp_path = tempfile.mkstemp(suffix = ".py", prefix = "studio_exec_", dir = workdir)
|
||||
with os.fdopen(fd, "w") as f:
|
||||
# utf-8 so non-ASCII in model-written code survives the OS default codec
|
||||
# (Windows cp1252 would otherwise raise UnicodeEncodeError).
|
||||
with os.fdopen(fd, "w", encoding = "utf-8") as f:
|
||||
f.write(code)
|
||||
|
||||
safe_env = _build_bypass_env(workdir) if disable_sandbox else _build_safe_env(workdir)
|
||||
if disable_sandbox:
|
||||
# Match the sandboxed Python path without changing bypass shell I/O.
|
||||
safe_env = dict(safe_env)
|
||||
safe_env["PYTHONIOENCODING"] = "utf-8"
|
||||
popen_kwargs = dict(
|
||||
stdout = subprocess.PIPE,
|
||||
stderr = subprocess.STDOUT,
|
||||
text = True,
|
||||
# Decode child output as utf-8 (it emits utf-8 via PYTHONIOENCODING);
|
||||
# replace so non-ASCII output never crashes the read on Windows.
|
||||
encoding = "utf-8",
|
||||
errors = "replace",
|
||||
cwd = workdir,
|
||||
env = safe_env,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ httpx
|
|||
httpcore
|
||||
certifi
|
||||
idna
|
||||
anyio>=3.0,<4.14.0 # 4.14+ breaks cancel scope on Py3.13 (#6483)
|
||||
anyio>=3.0,<4.14.0 # one consistent <4.14: 4.14's TaskHandle importers over a stale 4.13 _core/_tasks -> ImportError (#6483)
|
||||
sniffio
|
||||
h11
|
||||
|
||||
|
|
|
|||
|
|
@ -13,8 +13,11 @@ fastmcp>=3.0.2
|
|||
mcp>=1.24,<2
|
||||
websockets>=15.0.1
|
||||
|
||||
# anyio 4.14+ breaks cancel scope on Python 3.13 (#6483). Global cap so later
|
||||
# with-deps steps (studio.txt, data-designer-deps.txt) can't re-resolve it up.
|
||||
# Keep anyio on one consistent <4.14 line. anyio 4.14 added TaskHandle (imported
|
||||
# by __init__.py and the asyncio backend from _core/_tasks); a clean 4.14 is fine
|
||||
# on 3.13. The real failure (#6483) is a half-resolved install: a stale 4.13
|
||||
# _core/_tasks (no TaskHandle) under 4.14's importers raises ImportError and 500s
|
||||
# the server. Global cap so later with-deps steps can't re-resolve it up.
|
||||
anyio<4.14.0
|
||||
|
||||
pandas==2.3.3
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@
|
|||
# happens at runtime via the side-car venvs.
|
||||
transformers>=4.57.6
|
||||
|
||||
# mlx-vlm / mlx-lm pull anyio>=4.14, which conflicts with the constraints.txt
|
||||
# cap (anyio<4.14.0, #6483: 4.14+ breaks cancel scope on Python 3.13). A -c
|
||||
# constraint loses that conflict on macOS-arm and 4.14.0 gets installed; an
|
||||
# override wins it, so force anyio down here too.
|
||||
# mlx-vlm / mlx-lm pull anyio>=4.14, which fights the constraints.txt cap
|
||||
# (anyio<4.14.0). The -c constraint loses that fight on macOS-arm, leaving a
|
||||
# half-resolved anyio (4.14 importers over a stale 4.13 _core/_tasks with no
|
||||
# TaskHandle) that ImportErrors and 500s the server (#6483; clean 4.14 is fine,
|
||||
# it is the mix that breaks). An override wins the fight, so force one
|
||||
# consistent <4.14 here too.
|
||||
anyio<4.14.0
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ def test_python_bypass_uses_bypass_preexec_and_bypass_env(captured_popen, monkey
|
|||
assert captured_popen["kwargs"]["preexec_fn"] is tools._bypass_preexec
|
||||
env = captured_popen["kwargs"]["env"]
|
||||
assert env.get("HOSTVAR") == "benign-xyz"
|
||||
assert env.get("PYTHONIOENCODING") == "utf-8"
|
||||
assert "HF_TOKEN" not in env
|
||||
|
||||
|
||||
|
|
@ -151,9 +152,12 @@ def test_bash_blocklist_skipped_when_bypassed(captured_popen):
|
|||
|
||||
|
||||
@_POSIX_ONLY
|
||||
def test_bash_bypass_uses_bypass_preexec(captured_popen):
|
||||
def test_bash_bypass_uses_bypass_preexec(captured_popen, monkeypatch):
|
||||
# bypass inherits benign host vars; clear so we assert _bash_exec adds none.
|
||||
monkeypatch.delenv("PYTHONIOENCODING", raising = False)
|
||||
_bash_exec("echo hi", None, 5, "t", disable_sandbox = True)
|
||||
assert captured_popen["kwargs"]["preexec_fn"] is tools._bypass_preexec
|
||||
assert "PYTHONIOENCODING" not in captured_popen["kwargs"]["env"]
|
||||
|
||||
|
||||
# ── real end-to-end python execution under bypass ───────────────────
|
||||
|
|
|
|||
33
studio/backend/tests/test_exec_utf8.py
Normal file
33
studio/backend/tests/test_exec_utf8.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved.
|
||||
|
||||
"""_python_exec must round-trip non-ASCII output end to end.
|
||||
|
||||
Model-written code routinely contains non-ASCII (arrows, CJK, emoji). The temp
|
||||
script and the child's stdout pipe both have to be UTF-8 or it crashes/garbles
|
||||
on Windows, whose default codec is cp1252. Mirrors the report in
|
||||
unslothai/unsloth#6489. The child is ``python`` with PYTHONIOENCODING=utf-8, so
|
||||
it emits UTF-8 on every OS; this proves the round-trip on a UTF-8 host and
|
||||
guards against a regression to the OS default codec.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
_BACKEND_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(_BACKEND_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_BACKEND_ROOT))
|
||||
|
||||
from core.inference.tools import _python_exec
|
||||
|
||||
# Arrow, em-dash, accent, CJK, check mark, astral-plane emoji -- none encodable
|
||||
# in cp1252, so the OS default codec would raise on write or read.
|
||||
_UNICODE = "café — 数字 → ✓ 😀"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("disable_sandbox", [False, True])
|
||||
def test_python_exec_round_trips_non_ascii(disable_sandbox):
|
||||
out = _python_exec(f"print({_UNICODE!r})", disable_sandbox = disable_sandbox)
|
||||
assert _UNICODE in out, repr(out)
|
||||
Loading…
Add table
Add a link
Reference in a new issue