Pin utf-8 on shipping-code text I/O instead of the operator locale (#7486)
* Pin utf-8 on shipping-code text I/O instead of the operator locale 113 read_text/write_text/open call sites across unsloth, studio and unsloth_cli let locale.getencoding() decide the encoding. That is utf-8 on the Linux and macOS runners and cp1252 on a stock Windows install, so the same file decodes differently for a Windows user and silently produces mojibake or raises UnicodeDecodeError. Adds tests/test_runtime_text_encoding.py to keep it that way. It resolves openers through each file's own imports rather than a fixed list of module names, so an aliased tarfile.open or a local from PIL.Image import open is not asked for an encoding it does not take. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Scan tracked files only and resolve the unbound Path calling forms * Honour PEP 263 when scanning sources and migrate a legacy JSONL before appending * Scope guard imports lexically and only migrate a legacy file when it round-trips * Leave a legacy JSONL untouched and resolve path aliases in the foreign-opener check * Tighten comments --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
4a79d707c5
commit
3fd948eb95
39 changed files with 563 additions and 114 deletions
|
|
@ -44,7 +44,7 @@ def generate_bootstrap_password() -> str:
|
||||||
|
|
||||||
# Persisted from a previous run?
|
# Persisted from a previous run?
|
||||||
if _BOOTSTRAP_PW_PATH.is_file():
|
if _BOOTSTRAP_PW_PATH.is_file():
|
||||||
_bootstrap_password = _BOOTSTRAP_PW_PATH.read_text().strip()
|
_bootstrap_password = _BOOTSTRAP_PW_PATH.read_text(encoding = "utf-8").strip()
|
||||||
if _bootstrap_password:
|
if _bootstrap_password:
|
||||||
return _bootstrap_password
|
return _bootstrap_password
|
||||||
|
|
||||||
|
|
@ -57,7 +57,7 @@ def generate_bootstrap_password() -> str:
|
||||||
|
|
||||||
# Persist so the same passphrase survives restarts until password change.
|
# Persist so the same passphrase survives restarts until password change.
|
||||||
ensure_dir(_BOOTSTRAP_PW_PATH.parent)
|
ensure_dir(_BOOTSTRAP_PW_PATH.parent)
|
||||||
_BOOTSTRAP_PW_PATH.write_text(_bootstrap_password)
|
_BOOTSTRAP_PW_PATH.write_text(_bootstrap_password, encoding = "utf-8")
|
||||||
try:
|
try:
|
||||||
os.chmod(_BOOTSTRAP_PW_PATH, 0o600)
|
os.chmod(_BOOTSTRAP_PW_PATH, 0o600)
|
||||||
except OSError:
|
except OSError:
|
||||||
|
|
@ -76,7 +76,7 @@ def _load_bootstrap_password() -> Optional[str]:
|
||||||
global _bootstrap_password
|
global _bootstrap_password
|
||||||
_bootstrap_password = None
|
_bootstrap_password = None
|
||||||
if _BOOTSTRAP_PW_PATH.is_file():
|
if _BOOTSTRAP_PW_PATH.is_file():
|
||||||
bootstrap_password = _BOOTSTRAP_PW_PATH.read_text().strip()
|
bootstrap_password = _BOOTSTRAP_PW_PATH.read_text(encoding = "utf-8").strip()
|
||||||
if bootstrap_password:
|
if bootstrap_password:
|
||||||
_bootstrap_password = bootstrap_password
|
_bootstrap_password = bootstrap_password
|
||||||
return _bootstrap_password
|
return _bootstrap_password
|
||||||
|
|
@ -99,7 +99,7 @@ def clear_bootstrap_password() -> None:
|
||||||
# stale plaintext can't be re-seeded by generate_bootstrap_password()
|
# stale plaintext can't be re-seeded by generate_bootstrap_password()
|
||||||
# if a later reset-password deletes auth.db and re-validates it.
|
# if a later reset-password deletes auth.db and re-validates it.
|
||||||
try:
|
try:
|
||||||
_BOOTSTRAP_PW_PATH.write_text("")
|
_BOOTSTRAP_PW_PATH.write_text("", encoding = "utf-8")
|
||||||
cleared = True
|
cleared = True
|
||||||
except OSError:
|
except OSError:
|
||||||
cleared = False
|
cleared = False
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ def _store_colab_login_credentials(username: str, password: str) -> None:
|
||||||
path = _colab_login_credentials_path()
|
path = _colab_login_credentials_path()
|
||||||
try:
|
try:
|
||||||
path.parent.mkdir(parents = True, exist_ok = True)
|
path.parent.mkdir(parents = True, exist_ok = True)
|
||||||
path.write_text(f"{username}\n{password}\n")
|
path.write_text(f"{username}\n{password}\n", encoding = "utf-8")
|
||||||
try:
|
try:
|
||||||
import os
|
import os
|
||||||
os.chmod(path, 0o600)
|
os.chmod(path, 0o600)
|
||||||
|
|
@ -106,7 +106,7 @@ def _load_colab_login_credentials() -> "tuple[str, str] | None":
|
||||||
try:
|
try:
|
||||||
if not path.is_file():
|
if not path.is_file():
|
||||||
return None
|
return None
|
||||||
lines = path.read_text().splitlines()
|
lines = path.read_text(encoding = "utf-8").splitlines()
|
||||||
if len(lines) >= 2 and lines[0] and lines[1]:
|
if len(lines) >= 2 and lines[0] and lines[1]:
|
||||||
return lines[0], lines[1]
|
return lines[0], lines[1]
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
|
|
|
||||||
|
|
@ -241,7 +241,7 @@ def _offline_window_if(local_files_only):
|
||||||
def _is_wsl():
|
def _is_wsl():
|
||||||
"""Detect if running under Windows Subsystem for Linux."""
|
"""Detect if running under Windows Subsystem for Linux."""
|
||||||
try:
|
try:
|
||||||
return "microsoft" in open("/proc/version").read().lower()
|
return "microsoft" in open("/proc/version", encoding = "utf-8").read().lower()
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -574,7 +574,7 @@ class ExportBackend:
|
||||||
)
|
)
|
||||||
metadata = {"base_model": base_model}
|
metadata = {"base_model": base_model}
|
||||||
metadata_path = os.path.join(save_directory, "export_metadata.json")
|
metadata_path = os.path.join(save_directory, "export_metadata.json")
|
||||||
with open(metadata_path, "w") as f:
|
with open(metadata_path, "w", encoding = "utf-8") as f:
|
||||||
json.dump(metadata, f, indent = 2)
|
json.dump(metadata, f, indent = 2)
|
||||||
logger.info(f"Wrote export metadata to {metadata_path}")
|
logger.info(f"Wrote export metadata to {metadata_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -567,7 +567,7 @@ class InferenceBackend:
|
||||||
_meta_path = Path(config.path) / "export_metadata.json"
|
_meta_path = Path(config.path) / "export_metadata.json"
|
||||||
try:
|
try:
|
||||||
if _meta_path.exists():
|
if _meta_path.exists():
|
||||||
_meta = json.loads(_meta_path.read_text())
|
_meta = json.loads(_meta_path.read_text(encoding = "utf-8"))
|
||||||
if _meta.get("base_model"):
|
if _meta.get("base_model"):
|
||||||
processor_source = _meta["base_model"]
|
processor_source = _meta["base_model"]
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
|
||||||
|
|
@ -569,7 +569,7 @@ def _load_swa_cache() -> dict:
|
||||||
if _SWA_CACHE is not None:
|
if _SWA_CACHE is not None:
|
||||||
return _SWA_CACHE
|
return _SWA_CACHE
|
||||||
try:
|
try:
|
||||||
with open(_swa_cache_path()) as f:
|
with open(_swa_cache_path(), encoding = "utf-8") as f:
|
||||||
_SWA_CACHE = json.load(f)
|
_SWA_CACHE = json.load(f)
|
||||||
if not isinstance(_SWA_CACHE, dict):
|
if not isinstance(_SWA_CACHE, dict):
|
||||||
_SWA_CACHE = {}
|
_SWA_CACHE = {}
|
||||||
|
|
@ -583,7 +583,7 @@ def _save_swa_cache(cache: dict) -> None:
|
||||||
path = _swa_cache_path()
|
path = _swa_cache_path()
|
||||||
path.parent.mkdir(parents = True, exist_ok = True)
|
path.parent.mkdir(parents = True, exist_ok = True)
|
||||||
tmp = path.with_suffix(".json.tmp")
|
tmp = path.with_suffix(".json.tmp")
|
||||||
with open(tmp, "w") as f:
|
with open(tmp, "w", encoding = "utf-8") as f:
|
||||||
json.dump(cache, f, indent = 2, sort_keys = True)
|
json.dump(cache, f, indent = 2, sort_keys = True)
|
||||||
tmp.replace(path)
|
tmp.replace(path)
|
||||||
except OSError:
|
except OSError:
|
||||||
|
|
@ -620,7 +620,7 @@ def _fetch_swa_entry_from_hf(repo_id: str) -> Optional[object]:
|
||||||
repo_type = "model",
|
repo_type = "model",
|
||||||
cache_dir = active_hf_hub_cache(),
|
cache_dir = active_hf_hub_cache(),
|
||||||
)
|
)
|
||||||
with open(cfg_path) as f:
|
with open(cfg_path, encoding = "utf-8") as f:
|
||||||
cfg = json.load(f)
|
cfg = json.load(f)
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
@ -3596,7 +3596,7 @@ class LlamaCppBackend:
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
with open("/proc/meminfo") as f:
|
with open("/proc/meminfo", encoding = "utf-8") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
if line.startswith("MemAvailable:"):
|
if line.startswith("MemAvailable:"):
|
||||||
return int(line.split()[1]) // 1024 # kB -> MiB
|
return int(line.split()[1]) // 1024 # kB -> MiB
|
||||||
|
|
@ -9477,7 +9477,7 @@ class LlamaCppBackend:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
path.parent.mkdir(parents = True, exist_ok = True)
|
path.parent.mkdir(parents = True, exist_ok = True)
|
||||||
path.write_text(f"{pid}:{cls._pid_start_identity(pid)}")
|
path.write_text(f"{pid}:{cls._pid_start_identity(pid)}", encoding = "utf-8")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Could not write llama-server pidfile: {e}")
|
logger.debug(f"Could not write llama-server pidfile: {e}")
|
||||||
|
|
||||||
|
|
@ -9611,7 +9611,7 @@ class LlamaCppBackend:
|
||||||
pid = -1
|
pid = -1
|
||||||
identity = ""
|
identity = ""
|
||||||
try:
|
try:
|
||||||
pid_str, _, identity = path.read_text().strip().partition(":")
|
pid_str, _, identity = path.read_text(encoding = "utf-8").strip().partition(":")
|
||||||
pid = int(pid_str)
|
pid = int(pid_str)
|
||||||
except Exception:
|
except Exception:
|
||||||
pid = -1
|
pid = -1
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ def _load_sidecar(cwd):
|
||||||
"""Return the persisted ``source -> healed target`` map, or {} on any error
|
"""Return the persisted ``source -> healed target`` map, or {} on any error
|
||||||
(missing/corrupt/foreign sidecar degrades to in-process-only behaviour)."""
|
(missing/corrupt/foreign sidecar degrades to in-process-only behaviour)."""
|
||||||
try:
|
try:
|
||||||
with open(_sidecar_path(cwd)) as fh:
|
with open(_sidecar_path(cwd), encoding = "utf-8") as fh:
|
||||||
data = json.load(fh)
|
data = json.load(fh)
|
||||||
except Exception: # noqa: BLE001 - a bad sidecar must never break user code
|
except Exception: # noqa: BLE001 - a bad sidecar must never break user code
|
||||||
return {}
|
return {}
|
||||||
|
|
@ -131,7 +131,7 @@ def _record_sidecar(cwd, source, target):
|
||||||
return
|
return
|
||||||
data[source] = target
|
data[source] = target
|
||||||
tmp = _sidecar_path(cwd) + ".tmp"
|
tmp = _sidecar_path(cwd) + ".tmp"
|
||||||
with open(tmp, "w") as fh:
|
with open(tmp, "w", encoding = "utf-8") as fh:
|
||||||
json.dump(data, fh)
|
json.dump(data, fh)
|
||||||
os.replace(tmp, _sidecar_path(cwd))
|
os.replace(tmp, _sidecar_path(cwd))
|
||||||
except Exception: # noqa: BLE001 - persistence is best effort only
|
except Exception: # noqa: BLE001 - persistence is best effort only
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ def _resolve_lora_4bit(mc, load_in_4bit: bool) -> bool:
|
||||||
import json
|
import json
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(adapter_cfg_path) as f:
|
with open(adapter_cfg_path, encoding = "utf-8") as f:
|
||||||
adapter_cfg = json.load(f)
|
adapter_cfg = json.load(f)
|
||||||
training_method = adapter_cfg.get("unsloth_training_method")
|
training_method = adapter_cfg.get("unsloth_training_method")
|
||||||
if training_method == "lora" and load_in_4bit:
|
if training_method == "lora" and load_in_4bit:
|
||||||
|
|
@ -961,7 +961,10 @@ def run_inference_process(
|
||||||
if _local_adapter_cfg.is_file():
|
if _local_adapter_cfg.is_file():
|
||||||
try:
|
try:
|
||||||
_lora_base = (
|
_lora_base = (
|
||||||
_json.loads(_local_adapter_cfg.read_text()).get("base_model_name_or_path") or None
|
_json.loads(_local_adapter_cfg.read_text(encoding = "utf-8")).get(
|
||||||
|
"base_model_name_or_path"
|
||||||
|
)
|
||||||
|
or None
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
_lora_base = None
|
_lora_base = None
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ def _st_module_subdirs(name: str, token: str | None) -> tuple[str, ...]:
|
||||||
path = Path(normalize_path(name)).expanduser() / "modules.json"
|
path = Path(normalize_path(name)).expanduser() / "modules.json"
|
||||||
if not path.is_file():
|
if not path.is_file():
|
||||||
return ()
|
return ()
|
||||||
data = json.loads(path.read_text())
|
data = json.loads(path.read_text(encoding = "utf-8"))
|
||||||
else:
|
else:
|
||||||
from huggingface_hub import hf_hub_download
|
from huggingface_hub import hf_hub_download
|
||||||
from huggingface_hub.utils import EntryNotFoundError
|
from huggingface_hub.utils import EntryNotFoundError
|
||||||
|
|
@ -115,7 +115,7 @@ def _st_module_subdirs(name: str, token: str | None) -> tuple[str, ...]:
|
||||||
)
|
)
|
||||||
except EntryNotFoundError:
|
except EntryNotFoundError:
|
||||||
return ()
|
return ()
|
||||||
data = json.loads(open(local).read())
|
data = json.loads(open(local, encoding = "utf-8").read())
|
||||||
subdirs = []
|
subdirs = []
|
||||||
for module in data or ():
|
for module in data or ():
|
||||||
sub = str((module or {}).get("path", "")).strip().strip("/")
|
sub = str((module or {}).get("path", "")).strip().strip("/")
|
||||||
|
|
|
||||||
|
|
@ -215,7 +215,7 @@ def _ollama_model_info_from_manifest(
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
manifest = json.loads(tag_file.read_text())
|
manifest = json.loads(tag_file.read_text(encoding = "utf-8"))
|
||||||
except (json.JSONDecodeError, OSError) as e:
|
except (json.JSONDecodeError, OSError) as e:
|
||||||
logger.debug("Skipping unreadable/invalid Ollama manifest %s: %s", tag_file, e)
|
logger.debug("Skipping unreadable/invalid Ollama manifest %s: %s", tag_file, e)
|
||||||
return None
|
return None
|
||||||
|
|
@ -228,7 +228,7 @@ def _ollama_model_info_from_manifest(
|
||||||
config_blob = _ollama_blob_path(blobs_dir, config_digest)
|
config_blob = _ollama_blob_path(blobs_dir, config_digest)
|
||||||
if config_blob is not None and _safe_is_file(config_blob):
|
if config_blob is not None and _safe_is_file(config_blob):
|
||||||
try:
|
try:
|
||||||
cfg = json.loads(config_blob.read_text())
|
cfg = json.loads(config_blob.read_text(encoding = "utf-8"))
|
||||||
model_type = cfg.get("model_type", "")
|
model_type = cfg.get("model_type", "")
|
||||||
file_type = cfg.get("file_type", "")
|
file_type = cfg.get("file_type", "")
|
||||||
except (json.JSONDecodeError, OSError) as e:
|
except (json.JSONDecodeError, OSError) as e:
|
||||||
|
|
|
||||||
|
|
@ -462,7 +462,7 @@ def _read_marker_value(marker: Path) -> Optional[str]:
|
||||||
try:
|
try:
|
||||||
if not marker.exists():
|
if not marker.exists():
|
||||||
return None
|
return None
|
||||||
value = marker.read_text().strip()
|
value = marker.read_text(encoding = "utf-8").strip()
|
||||||
except OSError:
|
except OSError:
|
||||||
return None
|
return None
|
||||||
return value if value in VALID_TRANSPORTS else None
|
return value if value in VALID_TRANSPORTS else None
|
||||||
|
|
@ -473,7 +473,7 @@ def _write_marker_value(marker: Path, mode: str) -> None:
|
||||||
# tmp + rename so a SIGKILL mid-write can't leave a half-written marker.
|
# tmp + rename so a SIGKILL mid-write can't leave a half-written marker.
|
||||||
# The tmp name is per-process so concurrent writers don't clobber tmps.
|
# The tmp name is per-process so concurrent writers don't clobber tmps.
|
||||||
tmp = marker.with_name(f"{marker.name}.tmp-{os.getpid()}")
|
tmp = marker.with_name(f"{marker.name}.tmp-{os.getpid()}")
|
||||||
tmp.write_text(mode)
|
tmp.write_text(mode, encoding = "utf-8")
|
||||||
os.replace(tmp, marker)
|
os.replace(tmp, marker)
|
||||||
except OSError:
|
except OSError:
|
||||||
# Best-effort: a missing marker next run purges the partial defensively,
|
# Best-effort: a missing marker next run purges the partial defensively,
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ def _is_wsl() -> bool:
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
return "microsoft" in Path("/proc/version").read_text().lower()
|
return "microsoft" in Path("/proc/version").read_text(encoding = "utf-8").lower()
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -124,7 +124,7 @@ def _wsl_automount_root() -> str:
|
||||||
import configparser
|
import configparser
|
||||||
|
|
||||||
parser = configparser.ConfigParser(inline_comment_prefixes = ("#", ";"))
|
parser = configparser.ConfigParser(inline_comment_prefixes = ("#", ";"))
|
||||||
parser.read("/etc/wsl.conf")
|
parser.read("/etc/wsl.conf", encoding = "utf-8")
|
||||||
root = parser.get("automount", "root", fallback = "").strip().strip("\"'")
|
root = parser.get("automount", "root", fallback = "").strip().strip("\"'")
|
||||||
except Exception:
|
except Exception:
|
||||||
return default
|
return default
|
||||||
|
|
|
||||||
|
|
@ -254,7 +254,11 @@ def _read_studio_install_id() -> str:
|
||||||
/api/health emits "" and the launcher accepts any healthy backend.
|
/api/health emits "" and the launcher accepts any healthy backend.
|
||||||
Carries no install-path info (matters when Unsloth runs -H 0.0.0.0)."""
|
Carries no install-path info (matters when Unsloth runs -H 0.0.0.0)."""
|
||||||
try:
|
try:
|
||||||
token = (_STUDIO_ROOT_RESOLVED / "share" / "studio_install_id").read_text().strip()
|
token = (
|
||||||
|
(_STUDIO_ROOT_RESOLVED / "share" / "studio_install_id")
|
||||||
|
.read_text(encoding = "utf-8")
|
||||||
|
.strip()
|
||||||
|
)
|
||||||
except (OSError, ValueError):
|
except (OSError, ValueError):
|
||||||
return ""
|
return ""
|
||||||
return token if _STUDIO_INSTALL_ID_RE.fullmatch(token) else ""
|
return token if _STUDIO_INSTALL_ID_RE.fullmatch(token) else ""
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ class StateStore:
|
||||||
self._data: Dict[str, Any] = {}
|
self._data: Dict[str, Any] = {}
|
||||||
if self.path.exists():
|
if self.path.exists():
|
||||||
try:
|
try:
|
||||||
with self.path.open() as f:
|
with self.path.open(encoding = "utf-8") as f:
|
||||||
self._data = json.load(f)
|
self._data = json.load(f)
|
||||||
except Exception:
|
except Exception:
|
||||||
self._data = {}
|
self._data = {}
|
||||||
|
|
@ -51,7 +51,7 @@ class StateStore:
|
||||||
|
|
||||||
def _flush(self) -> None:
|
def _flush(self) -> None:
|
||||||
tmp = self.path.with_suffix(self.path.suffix + ".tmp")
|
tmp = self.path.with_suffix(self.path.suffix + ".tmp")
|
||||||
with tmp.open("w") as f:
|
with tmp.open("w", encoding = "utf-8") as f:
|
||||||
json.dump(self._data, f, indent = 2, default = str)
|
json.dump(self._data, f, indent = 2, default = str)
|
||||||
os.replace(tmp, self.path)
|
os.replace(tmp, self.path)
|
||||||
|
|
||||||
|
|
@ -63,12 +63,14 @@ class JsonlWriter:
|
||||||
self.path = Path(path)
|
self.path = Path(path)
|
||||||
self.path.parent.mkdir(parents = True, exist_ok = True)
|
self.path.parent.mkdir(parents = True, exist_ok = True)
|
||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
self._fh = self.path.open("a", buffering = 1)
|
self._fh = self.path.open("a", buffering = 1, encoding = "utf-8")
|
||||||
self._count_seen_keys: set[str] = set()
|
self._count_seen_keys: set[str] = set()
|
||||||
# Preload seen keys for dedup across resumes
|
# Preload seen keys for dedup across resumes
|
||||||
if self.path.exists() and self.path.stat().st_size > 0:
|
if self.path.exists() and self.path.stat().st_size > 0:
|
||||||
try:
|
try:
|
||||||
with self.path.open() as f:
|
# No guess is safe for a file an older build wrote in the
|
||||||
|
# operator's locale, so read past whatever will not decode.
|
||||||
|
with self.path.open(encoding = "utf-8", errors = "replace") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
try:
|
try:
|
||||||
obj = json.loads(line)
|
obj = json.loads(line)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ class UnstructuredSeedReader(SeedReader[UnstructuredSeedSource]):
|
||||||
orig_name = path_obj.name
|
orig_name = path_obj.name
|
||||||
if meta_path.exists():
|
if meta_path.exists():
|
||||||
try:
|
try:
|
||||||
meta = json_mod.loads(meta_path.read_text())
|
meta = json_mod.loads(meta_path.read_text(encoding = "utf-8"))
|
||||||
orig_name = meta.get("original_filename", path_obj.name)
|
orig_name = meta.get("original_filename", path_obj.name)
|
||||||
except (json_mod.JSONDecodeError, OSError):
|
except (json_mod.JSONDecodeError, OSError):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -3778,7 +3778,7 @@ def _effective_load_in_4bit(config: ModelConfig, requested: bool) -> bool:
|
||||||
if not adapter_cfg_path.exists():
|
if not adapter_cfg_path.exists():
|
||||||
return load_in_4bit
|
return load_in_4bit
|
||||||
try:
|
try:
|
||||||
with open(adapter_cfg_path) as f:
|
with open(adapter_cfg_path, encoding = "utf-8") as f:
|
||||||
adapter_cfg = json.load(f)
|
adapter_cfg = json.load(f)
|
||||||
if not isinstance(adapter_cfg, dict): # malformed -> keep requested
|
if not isinstance(adapter_cfg, dict): # malformed -> keep requested
|
||||||
return load_in_4bit
|
return load_in_4bit
|
||||||
|
|
|
||||||
|
|
@ -722,7 +722,7 @@ def _scan_ollama_dir(ollama_dir: Path, limit: Optional[int] = None) -> List[Loca
|
||||||
stem_hash = hashlib.sha256(manifest_key.encode()).hexdigest()[:10]
|
stem_hash = hashlib.sha256(manifest_key.encode()).hexdigest()[:10]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
manifest = json.loads(tag_file.read_text())
|
manifest = json.loads(tag_file.read_text(encoding = "utf-8"))
|
||||||
except (json.JSONDecodeError, OSError) as e:
|
except (json.JSONDecodeError, OSError) as e:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Skipping unreadable/invalid Ollama manifest %s: %s",
|
"Skipping unreadable/invalid Ollama manifest %s: %s",
|
||||||
|
|
@ -738,7 +738,7 @@ def _scan_ollama_dir(ollama_dir: Path, limit: Optional[int] = None) -> List[Loca
|
||||||
config_blob = blobs_dir / config_digest.replace(":", "-")
|
config_blob = blobs_dir / config_digest.replace(":", "-")
|
||||||
if config_blob.is_file():
|
if config_blob.is_file():
|
||||||
try:
|
try:
|
||||||
cfg = json.loads(config_blob.read_text())
|
cfg = json.loads(config_blob.read_text(encoding = "utf-8"))
|
||||||
model_type = cfg.get("model_type", "")
|
model_type = cfg.get("model_type", "")
|
||||||
file_type = cfg.get("file_type", "")
|
file_type = cfg.get("file_type", "")
|
||||||
except (json.JSONDecodeError, OSError) as e:
|
except (json.JSONDecodeError, OSError) as e:
|
||||||
|
|
@ -1042,7 +1042,7 @@ def _dir_has_downloaded_model(directory: Path, max_entries: int = 4000) -> bool:
|
||||||
if not m.is_file():
|
if not m.is_file():
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
manifest = json.loads(m.read_text())
|
manifest = json.loads(m.read_text(encoding = "utf-8"))
|
||||||
except (json.JSONDecodeError, OSError, ValueError):
|
except (json.JSONDecodeError, OSError, ValueError):
|
||||||
continue
|
continue
|
||||||
for layer in manifest.get("layers") or []:
|
for layer in manifest.get("layers") or []:
|
||||||
|
|
|
||||||
|
|
@ -774,7 +774,7 @@ def _write_pid_file():
|
||||||
"""Write the current process PID to the studio PID file."""
|
"""Write the current process PID to the studio PID file."""
|
||||||
try:
|
try:
|
||||||
_PID_FILE.parent.mkdir(parents = True, exist_ok = True)
|
_PID_FILE.parent.mkdir(parents = True, exist_ok = True)
|
||||||
_PID_FILE.write_text(str(os.getpid()))
|
_PID_FILE.write_text(str(os.getpid()), encoding = "utf-8")
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -783,7 +783,7 @@ def _remove_pid_file():
|
||||||
"""Remove the PID file if it belongs to this process."""
|
"""Remove the PID file if it belongs to this process."""
|
||||||
try:
|
try:
|
||||||
if _PID_FILE.is_file():
|
if _PID_FILE.is_file():
|
||||||
stored = _PID_FILE.read_text().strip()
|
stored = _PID_FILE.read_text(encoding = "utf-8").strip()
|
||||||
if stored == str(os.getpid()):
|
if stored == str(os.getpid()):
|
||||||
_PID_FILE.unlink(missing_ok = True)
|
_PID_FILE.unlink(missing_ok = True)
|
||||||
except OSError:
|
except OSError:
|
||||||
|
|
|
||||||
|
|
@ -776,7 +776,7 @@ def _rocm_linux_sysfs_gpu_busy_pct() -> Optional[float]:
|
||||||
files = glob.glob("/sys/class/drm/card*/device/gpu_busy_percent")
|
files = glob.glob("/sys/class/drm/card*/device/gpu_busy_percent")
|
||||||
if not files:
|
if not files:
|
||||||
return None
|
return None
|
||||||
values = [int(open(f).read().strip()) for f in files]
|
values = [int(open(f, encoding = "utf-8").read().strip()) for f in files]
|
||||||
return round(sum(values) / len(values), 1)
|
return round(sum(values) / len(values), 1)
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
@ -790,7 +790,7 @@ def _rocm_linux_sysfs_temp_c() -> Optional[float]:
|
||||||
files = glob.glob("/sys/class/drm/card*/device/hwmon/hwmon*/temp1_input")
|
files = glob.glob("/sys/class/drm/card*/device/hwmon/hwmon*/temp1_input")
|
||||||
if not files:
|
if not files:
|
||||||
return None
|
return None
|
||||||
temps = [int(open(f).read().strip()) / 1000.0 for f in files]
|
temps = [int(open(f, encoding = "utf-8").read().strip()) / 1000.0 for f in files]
|
||||||
return round(max(temps), 1)
|
return round(max(temps), 1)
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
@ -807,7 +807,9 @@ def _rocm_linux_sysfs_power_w() -> Optional[float]:
|
||||||
):
|
):
|
||||||
files = glob.glob(pattern)
|
files = glob.glob(pattern)
|
||||||
if files:
|
if files:
|
||||||
watts = sum(int(open(f).read().strip()) / 1_000_000.0 for f in files)
|
watts = sum(
|
||||||
|
int(open(f, encoding = "utf-8").read().strip()) / 1_000_000.0 for f in files
|
||||||
|
)
|
||||||
return round(watts, 1)
|
return round(watts, 1)
|
||||||
return None
|
return None
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
@ -852,8 +854,8 @@ def _rocm_linux_sysfs_vram_gb() -> tuple[Optional[float], Optional[float]]:
|
||||||
total_files = glob.glob("/sys/class/drm/card*/device/mem_info_vram_total")
|
total_files = glob.glob("/sys/class/drm/card*/device/mem_info_vram_total")
|
||||||
if not used_files or not total_files:
|
if not used_files or not total_files:
|
||||||
return None, None
|
return None, None
|
||||||
used_bytes = sum(int(open(f).read().strip()) for f in used_files)
|
used_bytes = sum(int(open(f, encoding = "utf-8").read().strip()) for f in used_files)
|
||||||
total_bytes = sum(int(open(f).read().strip()) for f in total_files)
|
total_bytes = sum(int(open(f, encoding = "utf-8").read().strip()) for f in total_files)
|
||||||
if total_bytes == 0:
|
if total_bytes == 0:
|
||||||
return None, None
|
return None, None
|
||||||
return round(used_bytes / (1024**3), 2), round(total_bytes / (1024**3), 2)
|
return round(used_bytes / (1024**3), 2), round(total_bytes / (1024**3), 2)
|
||||||
|
|
@ -893,7 +895,7 @@ def _rocm_kfd_gpu_pci_ids() -> list[str]:
|
||||||
continue
|
continue
|
||||||
props: dict[str, int] = {}
|
props: dict[str, int] = {}
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(node_dir, "properties")) as f:
|
with open(os.path.join(node_dir, "properties"), encoding = "utf-8") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
parts = line.split()
|
parts = line.split()
|
||||||
if len(parts) == 2:
|
if len(parts) == 2:
|
||||||
|
|
@ -979,9 +981,9 @@ def _rocm_linux_sysfs_vram_by_pci_gb() -> dict[str, tuple[float, float]]:
|
||||||
if not bdf:
|
if not bdf:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(dev_dir, "mem_info_vram_used")) as f:
|
with open(os.path.join(dev_dir, "mem_info_vram_used"), encoding = "utf-8") as f:
|
||||||
used_bytes = int(f.read().strip())
|
used_bytes = int(f.read().strip())
|
||||||
with open(os.path.join(dev_dir, "mem_info_vram_total")) as f:
|
with open(os.path.join(dev_dir, "mem_info_vram_total"), encoding = "utf-8") as f:
|
||||||
total_bytes = int(f.read().strip())
|
total_bytes = int(f.read().strip())
|
||||||
except (OSError, ValueError):
|
except (OSError, ValueError):
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ def _read_checkpoint_loss(checkpoint_path: Path) -> Optional[float]:
|
||||||
if not trainer_state.exists():
|
if not trainer_state.exists():
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
with open(trainer_state) as f:
|
with open(trainer_state, encoding = "utf-8") as f:
|
||||||
state = json.load(f)
|
state = json.load(f)
|
||||||
log_history = state.get("log_history", [])
|
log_history = state.get("log_history", [])
|
||||||
if log_history:
|
if log_history:
|
||||||
|
|
@ -174,18 +174,18 @@ def scan_checkpoints(
|
||||||
metadata: dict = {}
|
metadata: dict = {}
|
||||||
try:
|
try:
|
||||||
if adapter_config.exists():
|
if adapter_config.exists():
|
||||||
cfg = json.loads(adapter_config.read_text())
|
cfg = json.loads(adapter_config.read_text(encoding = "utf-8"))
|
||||||
metadata["base_model"] = cfg.get("base_model_name_or_path")
|
metadata["base_model"] = cfg.get("base_model_name_or_path")
|
||||||
metadata["peft_type"] = cfg.get("peft_type")
|
metadata["peft_type"] = cfg.get("peft_type")
|
||||||
metadata["lora_rank"] = cfg.get("r")
|
metadata["lora_rank"] = cfg.get("r")
|
||||||
elif config_file.exists():
|
elif config_file.exists():
|
||||||
cfg = json.loads(config_file.read_text())
|
cfg = json.loads(config_file.read_text(encoding = "utf-8"))
|
||||||
metadata["base_model"] = cfg.get("_name_or_path")
|
metadata["base_model"] = cfg.get("_name_or_path")
|
||||||
|
|
||||||
# Detect BNB quantization from config.json
|
# Detect BNB quantization from config.json
|
||||||
if config_file.exists():
|
if config_file.exists():
|
||||||
if "cfg" not in dir():
|
if "cfg" not in dir():
|
||||||
cfg = json.loads(config_file.read_text())
|
cfg = json.loads(config_file.read_text(encoding = "utf-8"))
|
||||||
quant_cfg = cfg.get("quantization_config")
|
quant_cfg = cfg.get("quantization_config")
|
||||||
if (
|
if (
|
||||||
isinstance(quant_cfg, dict)
|
isinstance(quant_cfg, dict)
|
||||||
|
|
|
||||||
|
|
@ -631,7 +631,7 @@ def _raw_config_has_vision_config(
|
||||||
cache_dir = active_hf_hub_cache(),
|
cache_dir = active_hf_hub_cache(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
config = json.loads(config_path.read_text())
|
config = json.loads(config_path.read_text(encoding = "utf-8"))
|
||||||
architectures = config.get("architectures") or []
|
architectures = config.get("architectures") or []
|
||||||
model_type = config.get("model_type")
|
model_type = config.get("model_type")
|
||||||
explicit_vision = (
|
explicit_vision = (
|
||||||
|
|
@ -1083,7 +1083,7 @@ def _detect_audio_from_tokenizer(
|
||||||
]:
|
]:
|
||||||
tok_file = snapshot / tok_path
|
tok_file = snapshot / tok_path
|
||||||
if tok_file.exists():
|
if tok_file.exists():
|
||||||
tok_config = json.loads(tok_file.read_text())
|
tok_config = json.loads(tok_file.read_text(encoding = "utf-8"))
|
||||||
read_any = True
|
read_any = True
|
||||||
result = _check_token_patterns(tok_config)
|
result = _check_token_patterns(tok_config)
|
||||||
if result:
|
if result:
|
||||||
|
|
@ -2283,7 +2283,7 @@ def scan_exported_models(
|
||||||
export_meta = run_dir / "export_metadata.json"
|
export_meta = run_dir / "export_metadata.json"
|
||||||
try:
|
try:
|
||||||
if export_meta.exists():
|
if export_meta.exists():
|
||||||
meta = json.loads(export_meta.read_text())
|
meta = json.loads(export_meta.read_text(encoding = "utf-8"))
|
||||||
base_model = meta.get("base_model")
|
base_model = meta.get("base_model")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
@ -2312,7 +2312,7 @@ def scan_exported_models(
|
||||||
if adapter_config.exists():
|
if adapter_config.exists():
|
||||||
export_type = "lora"
|
export_type = "lora"
|
||||||
try:
|
try:
|
||||||
cfg = json.loads(adapter_config.read_text())
|
cfg = json.loads(adapter_config.read_text(encoding = "utf-8"))
|
||||||
base_model = cfg.get("base_model_name_or_path")
|
base_model = cfg.get("base_model_name_or_path")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
@ -2321,7 +2321,7 @@ def scan_exported_models(
|
||||||
export_meta = checkpoint_dir / "export_metadata.json"
|
export_meta = checkpoint_dir / "export_metadata.json"
|
||||||
try:
|
try:
|
||||||
if export_meta.exists():
|
if export_meta.exists():
|
||||||
meta = json.loads(export_meta.read_text())
|
meta = json.loads(export_meta.read_text(encoding = "utf-8"))
|
||||||
base_model = meta.get("base_model")
|
base_model = meta.get("base_model")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
@ -2334,7 +2334,7 @@ def scan_exported_models(
|
||||||
export_meta = meta_dir / "export_metadata.json"
|
export_meta = meta_dir / "export_metadata.json"
|
||||||
try:
|
try:
|
||||||
if export_meta.exists():
|
if export_meta.exists():
|
||||||
meta = json.loads(export_meta.read_text())
|
meta = json.loads(export_meta.read_text(encoding = "utf-8"))
|
||||||
base_model = meta.get("base_model")
|
base_model = meta.get("base_model")
|
||||||
if base_model:
|
if base_model:
|
||||||
break
|
break
|
||||||
|
|
@ -2354,7 +2354,7 @@ def scan_exported_models(
|
||||||
outputs_adapter_cfg = resolve_output_dir(run_dir.name) / "adapter_config.json"
|
outputs_adapter_cfg = resolve_output_dir(run_dir.name) / "adapter_config.json"
|
||||||
try:
|
try:
|
||||||
if outputs_adapter_cfg.exists():
|
if outputs_adapter_cfg.exists():
|
||||||
cfg = json.loads(outputs_adapter_cfg.read_text())
|
cfg = json.loads(outputs_adapter_cfg.read_text(encoding = "utf-8"))
|
||||||
base_model = cfg.get("base_model_name_or_path")
|
base_model = cfg.get("base_model_name_or_path")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
@ -2380,7 +2380,7 @@ def get_base_model_from_checkpoint(checkpoint_path: str) -> Optional[str]:
|
||||||
|
|
||||||
adapter_config_path = checkpoint_path_obj / "adapter_config.json"
|
adapter_config_path = checkpoint_path_obj / "adapter_config.json"
|
||||||
if adapter_config_path.exists():
|
if adapter_config_path.exists():
|
||||||
with open(adapter_config_path, "r") as f:
|
with open(adapter_config_path, "r", encoding = "utf-8") as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
base_model = config.get("base_model_name_or_path")
|
base_model = config.get("base_model_name_or_path")
|
||||||
if base_model:
|
if base_model:
|
||||||
|
|
@ -2389,7 +2389,7 @@ def get_base_model_from_checkpoint(checkpoint_path: str) -> Optional[str]:
|
||||||
|
|
||||||
config_path = checkpoint_path_obj / "config.json"
|
config_path = checkpoint_path_obj / "config.json"
|
||||||
if config_path.exists():
|
if config_path.exists():
|
||||||
with open(config_path, "r") as f:
|
with open(config_path, "r", encoding = "utf-8") as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
for key in ("model_name", "_name_or_path"):
|
for key in ("model_name", "_name_or_path"):
|
||||||
base_model = config.get(key)
|
base_model = config.get(key)
|
||||||
|
|
@ -2445,7 +2445,7 @@ def get_base_model_from_lora(lora_path: str) -> Optional[str]:
|
||||||
# adapter_config.json first
|
# adapter_config.json first
|
||||||
adapter_config_path = lora_path_obj / "adapter_config.json"
|
adapter_config_path = lora_path_obj / "adapter_config.json"
|
||||||
if adapter_config_path.exists():
|
if adapter_config_path.exists():
|
||||||
with open(adapter_config_path, "r") as f:
|
with open(adapter_config_path, "r", encoding = "utf-8") as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
base_model = config.get("base_model_name_or_path")
|
base_model = config.get("base_model_name_or_path")
|
||||||
if base_model:
|
if base_model:
|
||||||
|
|
@ -2535,7 +2535,7 @@ def get_base_model_from_lora_identifier(
|
||||||
last_exc = exc
|
last_exc = exc
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
with open(cfg_path, "r") as f:
|
with open(cfg_path, "r", encoding = "utf-8") as f:
|
||||||
base_model = json.load(f).get("base_model_name_or_path")
|
base_model = json.load(f).get("base_model_name_or_path")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning("Could not parse adapter_config.json for '%s': %s", identifier, exc)
|
logger.warning("Could not parse adapter_config.json for '%s': %s", identifier, exc)
|
||||||
|
|
@ -2781,7 +2781,7 @@ class ModelConfig:
|
||||||
meta_path = gguf_dir / "export_metadata.json"
|
meta_path = gguf_dir / "export_metadata.json"
|
||||||
if meta_path.exists():
|
if meta_path.exists():
|
||||||
try:
|
try:
|
||||||
meta = json.loads(meta_path.read_text())
|
meta = json.loads(meta_path.read_text(encoding = "utf-8"))
|
||||||
base = meta.get("base_model")
|
base = meta.get("base_model")
|
||||||
if base and is_vision_model(base, hf_token = hf_token):
|
if base and is_vision_model(base, hf_token = hf_token):
|
||||||
base_is_vision = True
|
base_is_vision = True
|
||||||
|
|
@ -2912,7 +2912,7 @@ class ModelConfig:
|
||||||
token = hf_token,
|
token = hf_token,
|
||||||
cache_dir = active_hf_hub_cache(),
|
cache_dir = active_hf_hub_cache(),
|
||||||
)
|
)
|
||||||
with open(config_path, "r") as f:
|
with open(config_path, "r", encoding = "utf-8") as f:
|
||||||
adapter_config = json.load(f)
|
adapter_config = json.load(f)
|
||||||
base_model = adapter_config.get("base_model_name_or_path")
|
base_model = adapter_config.get("base_model_name_or_path")
|
||||||
if base_model:
|
if base_model:
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ def _is_wsl() -> bool:
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
with open("/proc/version", "r") as f:
|
with open("/proc/version", "r", encoding = "utf-8") as f:
|
||||||
return "microsoft" in f.read().lower()
|
return "microsoft" in f.read().lower()
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ def lmstudio_model_dirs() -> list[Path]:
|
||||||
settings_path = Path.home() / ".lmstudio" / "settings.json"
|
settings_path = Path.home() / ".lmstudio" / "settings.json"
|
||||||
if settings_path.is_file():
|
if settings_path.is_file():
|
||||||
try:
|
try:
|
||||||
with open(settings_path) as f:
|
with open(settings_path, encoding = "utf-8") as f:
|
||||||
settings = json.load(f)
|
settings = json.load(f)
|
||||||
downloads = settings.get("downloadsFolder", "")
|
downloads = settings.get("downloadsFolder", "")
|
||||||
if downloads:
|
if downloads:
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ def _load_remote_code_configs(model_name: str, hf_token: Optional[str] = None) -
|
||||||
for name in _REMOTE_CODE_CONFIG_FILES:
|
for name in _REMOTE_CODE_CONFIG_FILES:
|
||||||
p = root / name
|
p = root / name
|
||||||
if p.is_file():
|
if p.is_file():
|
||||||
configs.append(json.loads(p.read_text()))
|
configs.append(json.loads(p.read_text(encoding = "utf-8")))
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
from huggingface_hub import hf_hub_download
|
from huggingface_hub import hf_hub_download
|
||||||
|
|
@ -164,7 +164,7 @@ def _load_remote_code_configs(model_name: str, hf_token: Optional[str] = None) -
|
||||||
# Transient/auth failure is not "absent" -> fail closed to "unknown" so
|
# Transient/auth failure is not "absent" -> fail closed to "unknown" so
|
||||||
# the caller scans (a tokenizer/processor-only auto_map must not slip by).
|
# the caller scans (a tokenizer/processor-only auto_map must not slip by).
|
||||||
return None
|
return None
|
||||||
configs.append(json.loads(Path(p).read_text()))
|
configs.append(json.loads(Path(p).read_text(encoding = "utf-8")))
|
||||||
# Every config was read or a genuine 404 -> an empty list is a definitive
|
# Every config was read or a genuine 404 -> an empty list is a definitive
|
||||||
# "no auto_map", not "unknown".
|
# "no auto_map", not "unknown".
|
||||||
return configs
|
return configs
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,9 @@ def _indexed_shard_paths(
|
||||||
inconclusive = True # transient: an index that might exist could not be read
|
inconclusive = True # transient: an index that might exist could not be read
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
weight_map = (json.loads(open(index_path).read()) or {}).get("weight_map") or {}
|
weight_map = (json.loads(open(index_path, encoding = "utf-8").read()) or {}).get(
|
||||||
|
"weight_map"
|
||||||
|
) or {}
|
||||||
for shard in weight_map.values():
|
for shard in weight_map.values():
|
||||||
shard_norm = _normalize_repo_path(str(shard))
|
shard_norm = _normalize_repo_path(str(shard))
|
||||||
# weight_map paths are relative to the index file's directory.
|
# weight_map paths are relative to the index file's directory.
|
||||||
|
|
@ -326,7 +328,7 @@ def _st_load_roots(snapshot: Path) -> list:
|
||||||
roots = [snapshot]
|
roots = [snapshot]
|
||||||
try:
|
try:
|
||||||
import json
|
import json
|
||||||
modules = json.loads((snapshot / "modules.json").read_text())
|
modules = json.loads((snapshot / "modules.json").read_text(encoding = "utf-8"))
|
||||||
except (OSError, ValueError):
|
except (OSError, ValueError):
|
||||||
return roots # no / invalid modules.json -> snapshot root is the only load root
|
return roots # no / invalid modules.json -> snapshot root is the only load root
|
||||||
for module in modules or ():
|
for module in modules or ():
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ def approval_target_key(targets) -> str:
|
||||||
def _load() -> dict:
|
def _load() -> dict:
|
||||||
"""Parsed store, or an empty skeleton on any error (fail-safe = re-prompt)."""
|
"""Parsed store, or an empty skeleton on any error (fail-safe = re-prompt)."""
|
||||||
try:
|
try:
|
||||||
with open(_store_path()) as f:
|
with open(_store_path(), encoding = "utf-8") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
# Validate the shape, not just the version: a hand-edited ``subjects`` that is not a
|
# Validate the shape, not just the version: a hand-edited ``subjects`` that is not a
|
||||||
# dict (e.g. ``[]``) would otherwise crash lookup/record instead of failing safe.
|
# dict (e.g. ``[]``) would otherwise crash lookup/record instead of failing safe.
|
||||||
|
|
@ -92,7 +92,7 @@ def _save(data: dict) -> None:
|
||||||
storage_roots.ensure_dir(path.parent)
|
storage_roots.ensure_dir(path.parent)
|
||||||
tmp = path.parent / f".{path.name}.tmp-{os.getpid()}"
|
tmp = path.parent / f".{path.name}.tmp-{os.getpid()}"
|
||||||
try:
|
try:
|
||||||
with open(tmp, "w") as f:
|
with open(tmp, "w", encoding = "utf-8") as f:
|
||||||
json.dump(data, f, indent = 2)
|
json.dump(data, f, indent = 2)
|
||||||
try:
|
try:
|
||||||
os.chmod(tmp, 0o600)
|
os.chmod(tmp, 0o600)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ canonical scanner loads in-repo so the fallback never silently takes over.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import io
|
||||||
|
import tokenize
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
|
|
@ -392,6 +394,18 @@ def scan_remote_code_files(files: dict[str, str]) -> ScanResult:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _read_python_source(path) -> str:
|
||||||
|
"""Decode a .py the way Python will execute it: a PEP 263 cookie
|
||||||
|
(`# coding: cp1252`) wins, so forcing utf-8 would scan something other than
|
||||||
|
what runs."""
|
||||||
|
data = path.read_bytes()
|
||||||
|
try:
|
||||||
|
encoding = tokenize.detect_encoding(io.BytesIO(data).readline)[0]
|
||||||
|
except (SyntaxError, ValueError):
|
||||||
|
encoding = "utf-8"
|
||||||
|
return data.decode(encoding, errors = "replace")
|
||||||
|
|
||||||
|
|
||||||
def remote_code_fingerprint(files: dict[str, str]) -> str:
|
def remote_code_fingerprint(files: dict[str, str]) -> str:
|
||||||
"""Stable sha256 over the (sorted) file contents, for pinning consent."""
|
"""Stable sha256 over the (sorted) file contents, for pinning consent."""
|
||||||
h = hashlib.sha256()
|
h = hashlib.sha256()
|
||||||
|
|
@ -430,7 +444,7 @@ def repo_remote_code_files(model_name: str, hf_token: Optional[str] = None) -> d
|
||||||
# for an RCE gate (HIGH stays approvable; only CRITICAL hard-blocks).
|
# for an RCE gate (HIGH stays approvable; only CRITICAL hard-blocks).
|
||||||
for p in root.rglob("*.py"):
|
for p in root.rglob("*.py"):
|
||||||
if p.is_file():
|
if p.is_file():
|
||||||
files[str(p.relative_to(root))] = p.read_text(errors = "replace")
|
files[str(p.relative_to(root))] = _read_python_source(p)
|
||||||
# A local config can still point auto_map at an EXTERNAL Hub repo
|
# A local config can still point auto_map at an EXTERNAL Hub repo
|
||||||
# (owner/name--module.Class) that executes on load, so fetch it. Every config
|
# (owner/name--module.Class) that executes on load, so fetch it. Every config
|
||||||
# that can declare auto_map is checked, so a custom processor's external code
|
# that can declare auto_map is checked, so a custom processor's external code
|
||||||
|
|
@ -440,7 +454,7 @@ def repo_remote_code_files(model_name: str, hf_token: Optional[str] = None) -> d
|
||||||
p = root / name
|
p = root / name
|
||||||
if p.is_file():
|
if p.is_file():
|
||||||
try:
|
try:
|
||||||
ext_refs |= _auto_map_refs(json.loads(p.read_text()))
|
ext_refs |= _auto_map_refs(json.loads(p.read_text(encoding = "utf-8")))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
if not _add_external_refs(files, ext_refs, hf_token, model_name):
|
if not _add_external_refs(files, ext_refs, hf_token, model_name):
|
||||||
|
|
@ -469,7 +483,7 @@ def repo_remote_code_files(model_name: str, hf_token: Optional[str] = None) -> d
|
||||||
f"{model_name}: config {cfg_name} could not be fetched ({exc})"
|
f"{model_name}: config {cfg_name} could not be fetched ({exc})"
|
||||||
) from exc
|
) from exc
|
||||||
try:
|
try:
|
||||||
refs |= _auto_map_refs(json.loads(Path(cfg_path).read_text()))
|
refs |= _auto_map_refs(json.loads(Path(cfg_path).read_text(encoding = "utf-8")))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
own_refs = {fn for repo, fn in refs if repo is None}
|
own_refs = {fn for repo, fn in refs if repo is None}
|
||||||
|
|
@ -519,7 +533,7 @@ def repo_remote_code_files(model_name: str, hf_token: Optional[str] = None) -> d
|
||||||
raise RemoteCodeUnscannable(
|
raise RemoteCodeUnscannable(
|
||||||
f"{model_name}: present file {fn} could not be fetched ({exc})"
|
f"{model_name}: present file {fn} could not be fetched ({exc})"
|
||||||
) from exc
|
) from exc
|
||||||
files[fn] = Path(fp).read_text(errors = "replace")
|
files[fn] = _read_python_source(Path(fp))
|
||||||
# Code referenced from another repo executes too: scan it or fail closed.
|
# Code referenced from another repo executes too: scan it or fail closed.
|
||||||
if not _add_external_refs(files, refs, hf_token, model_name):
|
if not _add_external_refs(files, refs, hf_token, model_name):
|
||||||
raise RemoteCodeUnscannable(f"{model_name}: external auto_map code unreachable")
|
raise RemoteCodeUnscannable(f"{model_name}: external auto_map code unreachable")
|
||||||
|
|
@ -602,7 +616,7 @@ def external_auto_map_repos(model_name: str, hf_token: Optional[str] = None) ->
|
||||||
if not p.is_file():
|
if not p.is_file():
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
refs = _auto_map_refs(json.loads(p.read_text()))
|
refs = _auto_map_refs(json.loads(p.read_text(encoding = "utf-8")))
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
repos.update(repo for repo, _fn in refs if repo)
|
repos.update(repo for repo, _fn in refs if repo)
|
||||||
|
|
@ -624,7 +638,7 @@ def external_auto_map_repos(model_name: str, hf_token: Optional[str] = None) ->
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
refs = _auto_map_refs(json.loads(Path(cfg_path).read_text()))
|
refs = _auto_map_refs(json.loads(Path(cfg_path).read_text(encoding = "utf-8")))
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
repos.update(repo for repo, _fn in refs if repo)
|
repos.update(repo for repo, _fn in refs if repo)
|
||||||
|
|
@ -701,5 +715,5 @@ def _add_external_refs(files: dict, refs, hf_token, model_name: str) -> bool:
|
||||||
exc,
|
exc,
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
files[f"{repo}--{fn}"] = Path(fp).read_text(errors = "replace")
|
files[f"{repo}--{fn}"] = _read_python_source(Path(fp))
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
|
|
@ -420,7 +420,7 @@ def _resolve_base_model(model_name: str) -> str:
|
||||||
adapter_cfg_path = local_path / "adapter_config.json"
|
adapter_cfg_path = local_path / "adapter_config.json"
|
||||||
if _safe_is_file(adapter_cfg_path):
|
if _safe_is_file(adapter_cfg_path):
|
||||||
try:
|
try:
|
||||||
with open(adapter_cfg_path) as f:
|
with open(adapter_cfg_path, encoding = "utf-8") as f:
|
||||||
cfg = json.load(f)
|
cfg = json.load(f)
|
||||||
base = cfg.get("base_model_name_or_path")
|
base = cfg.get("base_model_name_or_path")
|
||||||
if base:
|
if base:
|
||||||
|
|
@ -437,7 +437,7 @@ def _resolve_base_model(model_name: str) -> str:
|
||||||
config_json_path = local_path / "config.json"
|
config_json_path = local_path / "config.json"
|
||||||
if _safe_is_file(config_json_path):
|
if _safe_is_file(config_json_path):
|
||||||
try:
|
try:
|
||||||
with open(config_json_path) as f:
|
with open(config_json_path, encoding = "utf-8") as f:
|
||||||
cfg = json.load(f)
|
cfg = json.load(f)
|
||||||
# Unsloth writes model_name, HF writes _name_or_path; skip a self-reference.
|
# Unsloth writes model_name, HF writes _name_or_path; skip a self-reference.
|
||||||
for _key in ("model_name", "_name_or_path"):
|
for _key in ("model_name", "_name_or_path"):
|
||||||
|
|
@ -534,14 +534,19 @@ def _adapter_base_from_hf_cache(model_name: str) -> str | None:
|
||||||
try:
|
try:
|
||||||
if ref_main.is_file():
|
if ref_main.is_file():
|
||||||
candidates.append(
|
candidates.append(
|
||||||
repo_dir / "snapshots" / ref_main.read_text().strip() / "adapter_config.json"
|
repo_dir
|
||||||
|
/ "snapshots"
|
||||||
|
/ ref_main.read_text(encoding = "utf-8").strip()
|
||||||
|
/ "adapter_config.json"
|
||||||
)
|
)
|
||||||
candidates += sorted(
|
candidates += sorted(
|
||||||
repo_dir.glob("snapshots/*/adapter_config.json"), key = _mtime, reverse = True
|
repo_dir.glob("snapshots/*/adapter_config.json"), key = _mtime, reverse = True
|
||||||
)
|
)
|
||||||
for cfg_path in candidates:
|
for cfg_path in candidates:
|
||||||
if cfg_path.is_file():
|
if cfg_path.is_file():
|
||||||
base = json.loads(cfg_path.read_text()).get("base_model_name_or_path")
|
base = json.loads(cfg_path.read_text(encoding = "utf-8")).get(
|
||||||
|
"base_model_name_or_path"
|
||||||
|
)
|
||||||
return base or None
|
return base or None
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.debug("HF cache adapter_config.json lookup failed for '%s': %s", model_name, exc)
|
logger.debug("HF cache adapter_config.json lookup failed for '%s': %s", model_name, exc)
|
||||||
|
|
@ -611,7 +616,7 @@ def _check_tokenizer_config_needs_v5(model_name: str, hf_token: str | None = Non
|
||||||
local_tc = local_path / "tokenizer_config.json"
|
local_tc = local_path / "tokenizer_config.json"
|
||||||
if _safe_is_file(local_tc):
|
if _safe_is_file(local_tc):
|
||||||
try:
|
try:
|
||||||
with open(local_tc) as f:
|
with open(local_tc, encoding = "utf-8") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
tokenizer_class = data.get("tokenizer_class", "")
|
tokenizer_class = data.get("tokenizer_class", "")
|
||||||
result = tokenizer_class in _TRANSFORMERS_5_TOKENIZER_CLASSES
|
result = tokenizer_class in _TRANSFORMERS_5_TOKENIZER_CLASSES
|
||||||
|
|
@ -688,7 +693,12 @@ def _config_json_from_hf_cache(model_name: str) -> dict | None:
|
||||||
ref_main = repo_dir / "refs" / "main"
|
ref_main = repo_dir / "refs" / "main"
|
||||||
try:
|
try:
|
||||||
if ref_main.is_file():
|
if ref_main.is_file():
|
||||||
candidates.append(repo_dir / "snapshots" / ref_main.read_text().strip() / "config.json")
|
candidates.append(
|
||||||
|
repo_dir
|
||||||
|
/ "snapshots"
|
||||||
|
/ ref_main.read_text(encoding = "utf-8").strip()
|
||||||
|
/ "config.json"
|
||||||
|
)
|
||||||
# No refs/main (e.g. commit-pinned downloads): newest snapshot by mtime, not a stale
|
# No refs/main (e.g. commit-pinned downloads): newest snapshot by mtime, not a stale
|
||||||
# lexicographically-first SHA, matching what the Hub cache would actually load.
|
# lexicographically-first SHA, matching what the Hub cache would actually load.
|
||||||
candidates += sorted(
|
candidates += sorted(
|
||||||
|
|
@ -696,7 +706,7 @@ def _config_json_from_hf_cache(model_name: str) -> dict | None:
|
||||||
)
|
)
|
||||||
for cfg_path in candidates:
|
for cfg_path in candidates:
|
||||||
if cfg_path.is_file():
|
if cfg_path.is_file():
|
||||||
with open(cfg_path) as f:
|
with open(cfg_path, encoding = "utf-8") as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.debug("HF cache config.json lookup failed for '%s': %s", model_name, exc)
|
logger.debug("HF cache config.json lookup failed for '%s': %s", model_name, exc)
|
||||||
|
|
@ -721,7 +731,7 @@ def _load_config_json(model_name: str, hf_token: str | None = None) -> dict | No
|
||||||
local_cfg = Path(model_name) / "config.json"
|
local_cfg = Path(model_name) / "config.json"
|
||||||
if _safe_is_file(local_cfg):
|
if _safe_is_file(local_cfg):
|
||||||
try:
|
try:
|
||||||
with open(local_cfg) as f:
|
with open(local_cfg, encoding = "utf-8") as f:
|
||||||
cfg = json.load(f)
|
cfg = json.load(f)
|
||||||
_config_json_cache[cache_key] = cfg
|
_config_json_cache[cache_key] = cfg
|
||||||
return cfg
|
return cfg
|
||||||
|
|
@ -1755,7 +1765,7 @@ def _venv_dir_is_valid(venv_dir: str, packages: tuple[str, ...]) -> bool:
|
||||||
metadata = di / "METADATA"
|
metadata = di / "METADATA"
|
||||||
if not metadata.is_file():
|
if not metadata.is_file():
|
||||||
continue
|
continue
|
||||||
for line in metadata.read_text(errors = "replace").splitlines():
|
for line in metadata.read_text(errors = "replace", encoding = "utf-8").splitlines():
|
||||||
if line.startswith("Version:"):
|
if line.startswith("Version:"):
|
||||||
installed_ver = line.split(":", 1)[1].strip()
|
installed_ver = line.split(":", 1)[1].strip()
|
||||||
if installed_ver != pkg_version:
|
if installed_ver != pkg_version:
|
||||||
|
|
@ -2391,7 +2401,10 @@ def _llmcompressor_shadow_is_valid() -> bool:
|
||||||
"""True if the shadow dir exists with a marker matching the current pin fingerprint."""
|
"""True if the shadow dir exists with a marker matching the current pin fingerprint."""
|
||||||
marker = Path(_VENV_LLMCOMPRESSOR_DIR) / _LLMC_SHADOW_MARKER
|
marker = Path(_VENV_LLMCOMPRESSOR_DIR) / _LLMC_SHADOW_MARKER
|
||||||
try:
|
try:
|
||||||
return marker.is_file() and marker.read_text().strip() == _LLMC_SHADOW_FINGERPRINT
|
return (
|
||||||
|
marker.is_file()
|
||||||
|
and marker.read_text(encoding = "utf-8").strip() == _LLMC_SHADOW_FINGERPRINT
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -2460,7 +2473,7 @@ def _ensure_venv_llmcompressor_exists() -> bool:
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
try:
|
try:
|
||||||
(Path(_VENV_LLMCOMPRESSOR_DIR) / _LLMC_SHADOW_MARKER).write_text(
|
(Path(_VENV_LLMCOMPRESSOR_DIR) / _LLMC_SHADOW_MARKER).write_text(
|
||||||
_LLMC_SHADOW_FINGERPRINT
|
_LLMC_SHADOW_FINGERPRINT, encoding = "utf-8"
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ def hf_cache_snapshot_dir(model_name: str) -> Optional[Path]:
|
||||||
ref = repo_dir / "refs" / "main"
|
ref = repo_dir / "refs" / "main"
|
||||||
if not ref.is_file():
|
if not ref.is_file():
|
||||||
continue
|
continue
|
||||||
commit = ref.read_text().strip()
|
commit = ref.read_text(encoding = "utf-8").strip()
|
||||||
if not commit:
|
if not commit:
|
||||||
continue
|
continue
|
||||||
snapshot = repo_dir / "snapshots" / commit
|
snapshot = repo_dir / "snapshots" / commit
|
||||||
|
|
|
||||||
|
|
@ -2504,7 +2504,7 @@ def detect_host() -> HostInfo:
|
||||||
if is_linux:
|
if is_linux:
|
||||||
for _vendor_file in glob.glob("/sys/class/drm/card*/device/vendor"):
|
for _vendor_file in glob.glob("/sys/class/drm/card*/device/vendor"):
|
||||||
try:
|
try:
|
||||||
with open(_vendor_file) as _vf:
|
with open(_vendor_file, encoding = "utf-8") as _vf:
|
||||||
if _vf.read().strip().lower() == "0x8086":
|
if _vf.read().strip().lower() == "0x8086":
|
||||||
has_intel_gpu = True
|
has_intel_gpu = True
|
||||||
break
|
break
|
||||||
|
|
@ -3050,7 +3050,7 @@ def _detect_host_rocm_version() -> tuple[int, int] | None:
|
||||||
os.path.join(rocm_root, "lib", "rocm_version"),
|
os.path.join(rocm_root, "lib", "rocm_version"),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
with open(path) as fh:
|
with open(path, encoding = "utf-8") as fh:
|
||||||
parts = fh.read().strip().split("-")[0].split(".")
|
parts = fh.read().strip().split("-")[0].split(".")
|
||||||
# Explicit length guard avoids relying on the broad except
|
# Explicit length guard avoids relying on the broad except
|
||||||
# below to swallow IndexError when the version file contains
|
# below to swallow IndexError when the version file contains
|
||||||
|
|
@ -5526,7 +5526,9 @@ def write_prebuilt_metadata(
|
||||||
"prebuilt_fallback_used": prebuilt_fallback_used,
|
"prebuilt_fallback_used": prebuilt_fallback_used,
|
||||||
"installed_at_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
"installed_at_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||||
}
|
}
|
||||||
(install_dir / "UNSLOTH_PREBUILT_INFO.json").write_text(json.dumps(metadata, indent = 2) + "\n")
|
(install_dir / "UNSLOTH_PREBUILT_INFO.json").write_text(
|
||||||
|
json.dumps(metadata, indent = 2) + "\n", encoding = "utf-8"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def sync_marker_force_cpu(install_dir: Path, persist_force_cpu: bool) -> None:
|
def sync_marker_force_cpu(install_dir: Path, persist_force_cpu: bool) -> None:
|
||||||
|
|
@ -5537,13 +5539,13 @@ def sync_marker_force_cpu(install_dir: Path, persist_force_cpu: bool) -> None:
|
||||||
GPU/Vulkan bundle that revives the crash (#7213)."""
|
GPU/Vulkan bundle that revives the crash (#7213)."""
|
||||||
marker_path = install_dir / "UNSLOTH_PREBUILT_INFO.json"
|
marker_path = install_dir / "UNSLOTH_PREBUILT_INFO.json"
|
||||||
try:
|
try:
|
||||||
marker = json.loads(marker_path.read_text())
|
marker = json.loads(marker_path.read_text(encoding = "utf-8"))
|
||||||
except (OSError, ValueError):
|
except (OSError, ValueError):
|
||||||
return
|
return
|
||||||
if not isinstance(marker, dict) or bool(marker.get("force_cpu")) == persist_force_cpu:
|
if not isinstance(marker, dict) or bool(marker.get("force_cpu")) == persist_force_cpu:
|
||||||
return
|
return
|
||||||
marker["force_cpu"] = persist_force_cpu
|
marker["force_cpu"] = persist_force_cpu
|
||||||
marker_path.write_text(json.dumps(marker, indent = 2) + "\n")
|
marker_path.write_text(json.dumps(marker, indent = 2) + "\n", encoding = "utf-8")
|
||||||
log(f"existing install reused; recorded force_cpu={persist_force_cpu} from this run")
|
log(f"existing install reused; recorded force_cpu={persist_force_cpu} from this run")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -535,7 +535,7 @@ def install_lock(lock_path: Path) -> Iterator[None]:
|
||||||
break
|
break
|
||||||
except FileExistsError:
|
except FileExistsError:
|
||||||
try:
|
try:
|
||||||
raw = lock_path.read_text().strip()
|
raw = lock_path.read_text(encoding = "utf-8").strip()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
continue
|
continue
|
||||||
stale = False
|
stale = False
|
||||||
|
|
@ -660,7 +660,7 @@ def write_metadata(install_dir: Path, *, version: str, asset: str, sha256: str)
|
||||||
"asset": asset,
|
"asset": asset,
|
||||||
"sha256": sha256,
|
"sha256": sha256,
|
||||||
}
|
}
|
||||||
metadata_path(install_dir).write_text(json.dumps(payload, indent = 2) + "\n")
|
metadata_path(install_dir).write_text(json.dumps(payload, indent = 2) + "\n", encoding = "utf-8")
|
||||||
|
|
||||||
|
|
||||||
def load_metadata(install_dir: Path) -> dict | None:
|
def load_metadata(install_dir: Path) -> dict | None:
|
||||||
|
|
@ -668,7 +668,7 @@ def load_metadata(install_dir: Path) -> dict | None:
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
data = json.loads(path.read_text())
|
data = json.loads(path.read_text(encoding = "utf-8"))
|
||||||
except (json.JSONDecodeError, OSError):
|
except (json.JSONDecodeError, OSError):
|
||||||
return None
|
return None
|
||||||
return data if isinstance(data, dict) else None
|
return data if isinstance(data, dict) else None
|
||||||
|
|
|
||||||
|
|
@ -503,7 +503,7 @@ def _detect_rocm_version() -> tuple[int, int] | None:
|
||||||
os.path.join(rocm_root, "lib", "rocm_version"),
|
os.path.join(rocm_root, "lib", "rocm_version"),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
with open(path) as fh:
|
with open(path, encoding = "utf-8") as fh:
|
||||||
parts = fh.read().strip().split("-")[0].split(".")
|
parts = fh.read().strip().split("-")[0].split(".")
|
||||||
# Explicit length guard: don't rely on the broad except below to
|
# Explicit length guard: don't rely on the broad except below to
|
||||||
# swallow IndexError on a single-component version (e.g. "6\n").
|
# swallow IndexError on a single-component version (e.g. "6\n").
|
||||||
|
|
@ -852,9 +852,9 @@ def _linux_amd_display_device_present() -> bool:
|
||||||
try:
|
try:
|
||||||
for dev in Path("/sys/bus/pci/devices").iterdir():
|
for dev in Path("/sys/bus/pci/devices").iterdir():
|
||||||
try:
|
try:
|
||||||
if (dev / "vendor").read_text().strip() != "0x1002":
|
if (dev / "vendor").read_text(encoding = "utf-8").strip() != "0x1002":
|
||||||
continue
|
continue
|
||||||
if (dev / "class").read_text().strip().startswith("0x03"):
|
if (dev / "class").read_text(encoding = "utf-8").strip().startswith("0x03"):
|
||||||
return True
|
return True
|
||||||
except OSError:
|
except OSError:
|
||||||
continue
|
continue
|
||||||
|
|
@ -1067,7 +1067,7 @@ def _has_rocm_gpu() -> bool:
|
||||||
for entry in os.listdir(kfd_nodes):
|
for entry in os.listdir(kfd_nodes):
|
||||||
gpu_id_path = os.path.join(kfd_nodes, entry, "gpu_id")
|
gpu_id_path = os.path.join(kfd_nodes, entry, "gpu_id")
|
||||||
try:
|
try:
|
||||||
with open(gpu_id_path) as fh:
|
with open(gpu_id_path, encoding = "utf-8") as fh:
|
||||||
gpu_id = fh.read().strip()
|
gpu_id = fh.read().strip()
|
||||||
except OSError:
|
except OSError:
|
||||||
continue
|
continue
|
||||||
|
|
@ -1079,7 +1079,7 @@ def _has_rocm_gpu() -> bool:
|
||||||
# false positive (e.g. NVIDIA open-driver KFD nodes lacking it).
|
# false positive (e.g. NVIDIA open-driver KFD nodes lacking it).
|
||||||
props_path = os.path.join(kfd_nodes, entry, "properties")
|
props_path = os.path.join(kfd_nodes, entry, "properties")
|
||||||
try:
|
try:
|
||||||
with open(props_path) as fh:
|
with open(props_path, encoding = "utf-8") as fh:
|
||||||
props = fh.read()
|
props = fh.read()
|
||||||
except OSError:
|
except OSError:
|
||||||
continue # can't confirm vendor -- skip
|
continue # can't confirm vendor -- skip
|
||||||
|
|
|
||||||
|
|
@ -1078,7 +1078,7 @@ def install_lock(lock_path: Path) -> Iterator[None]:
|
||||||
except FileExistsError:
|
except FileExistsError:
|
||||||
stale = False
|
stale = False
|
||||||
try:
|
try:
|
||||||
raw = lock_path.read_text().strip()
|
raw = lock_path.read_text(encoding = "utf-8").strip()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
# Lock vanished between our open and read -- retry
|
# Lock vanished between our open and read -- retry
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
407
tests/test_runtime_text_encoding.py
Normal file
407
tests/test_runtime_text_encoding.py
Normal file
|
|
@ -0,0 +1,407 @@
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
||||||
|
|
||||||
|
"""Guard: shipping code must name an encoding on every text read and write.
|
||||||
|
|
||||||
|
`Path.read_text()`, `Path.write_text()`, `Path.open()` and builtin `open()` fall back
|
||||||
|
to `locale.getencoding()`: UTF-8 on the Linux and macOS runners, cp1252 on a stock
|
||||||
|
Windows install. Every file this repo reads at runtime is UTF-8 (HF `config.json` /
|
||||||
|
`tokenizer_config.json` / `adapter_config.json`, Ollama manifests, GGUF export
|
||||||
|
metadata), so on Windows those reads crash or, worse, succeed with mojibake: a
|
||||||
|
DeepSeek or Qwen tokenizer_config.json carries U+FF5C and U+2581 in its chat
|
||||||
|
template, and at utils/models/model_config.py that read sits inside a broad
|
||||||
|
`except Exception: logger.debug(...)`, so the token-pattern check silently
|
||||||
|
returned the wrong answer.
|
||||||
|
|
||||||
|
Unlike the import-time rule in test_source_read_encoding.py this is scope agnostic:
|
||||||
|
runtime reads live inside functions, and shipping code has no legitimate reason to
|
||||||
|
let the operator's locale decide. No reachability analysis to get wrong, so no
|
||||||
|
allowlist and no false positives.
|
||||||
|
|
||||||
|
Binary handles are skipped (no encoding to name, and passing one is a ValueError),
|
||||||
|
and a non-constant mode counts as unknown rather than text: demanding `encoding =`
|
||||||
|
on a call that may resolve to "rb" would leave no compliant way to write it.
|
||||||
|
|
||||||
|
Known limitation, deliberately not closed: `configparser.ConfigParser.read()` also
|
||||||
|
defaults to the locale encoding, but cannot be matched by name without resolving the
|
||||||
|
receiver, since `f.read(n)`, `resp.read(limit)` and `handle.read(chunk)` are spelled
|
||||||
|
identically. Flagging it would be a false positive with no compliant fix, the exact
|
||||||
|
failure mode this guard avoids. The one live `ConfigParser.read` (/etc/wsl.conf,
|
||||||
|
hub/utils/paths.py) is pinned by hand; a future one has to be caught in review.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# `str | None` below is evaluated at import on Python 3.9 (requires-python >= 3.9).
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
REPO = Path(__file__).resolve().parent.parent
|
||||||
|
# Everything that ships. `studio/` covers the installers too: install_python_stack.py
|
||||||
|
# reads /sys/class/kfd, the same detection path as utils/hardware/hardware.py. Test
|
||||||
|
# trees fall under the narrower import-time rule in test_source_read_encoding.py.
|
||||||
|
ROOTS = (REPO / "unsloth", REPO / "studio", REPO / "unsloth_cli")
|
||||||
|
# The frontend tree is TypeScript; node_modules is vendored third-party code.
|
||||||
|
SKIP_DIRS = {"build", "dist", "frontend", "node_modules", "src-tauri", ".venv", "site-packages"}
|
||||||
|
GUARDED_METHODS = {"read_text", "write_text"}
|
||||||
|
# Path classes, so an unbound `Path.open(p)` shifts every argument one right.
|
||||||
|
PATH_CLASSES = {"Path", "PosixPath", "PurePath", "WindowsPath"}
|
||||||
|
# Values that re-select the platform default when passed as the encoding.
|
||||||
|
PLATFORM_DEFAULT_ENCODINGS = (None, "locale")
|
||||||
|
# Calls that return the platform default, so naming one pins nothing.
|
||||||
|
PLATFORM_DEFAULT_CALLS = {"getdefaultencoding", "getencoding", "getpreferredencoding"}
|
||||||
|
# Modules whose `open` IS the builtin: same signature, same platform default.
|
||||||
|
BUILTIN_OPEN_MODULES = {"builtins", "io"}
|
||||||
|
# Take an encoding in "t" mode but default to "rb". Value is its positional slot.
|
||||||
|
COMPRESSED_OPENERS = {"bz2": 3, "gzip": 3, "lzma": None}
|
||||||
|
# Distinct from None so that "no mode argument at all" still means text.
|
||||||
|
UNKNOWN_MODE = object()
|
||||||
|
|
||||||
|
|
||||||
|
def _mode(call: ast.Call, positional_index: int):
|
||||||
|
"""The call's mode, or UNKNOWN_MODE when it is not a literal."""
|
||||||
|
# A splat hides the mode, so it is unknown rather than absent: falling through to
|
||||||
|
# "r" would flag a call that may resolve to binary, with no compliant way to fix it.
|
||||||
|
if any(isinstance(a, ast.Starred) for a in call.args):
|
||||||
|
return UNKNOWN_MODE
|
||||||
|
if any(kw.arg is None for kw in call.keywords):
|
||||||
|
return UNKNOWN_MODE
|
||||||
|
if len(call.args) > positional_index:
|
||||||
|
node = call.args[positional_index]
|
||||||
|
return node.value if isinstance(node, ast.Constant) else UNKNOWN_MODE
|
||||||
|
for kw in call.keywords:
|
||||||
|
if kw.arg == "mode":
|
||||||
|
return kw.value.value if isinstance(kw.value, ast.Constant) else UNKNOWN_MODE
|
||||||
|
return "r"
|
||||||
|
|
||||||
|
|
||||||
|
def _names_encoding(call: ast.Call) -> bool:
|
||||||
|
"""True only for an encoding that actually pins one.
|
||||||
|
|
||||||
|
`encoding = None` and `encoding = "locale"` re-select the platform default, so the
|
||||||
|
keyword being present is not enough. A `**kwargs` splat may carry an encoding we
|
||||||
|
cannot see, so it counts as named rather than as an unsatisfiable demand.
|
||||||
|
"""
|
||||||
|
for kw in call.keywords:
|
||||||
|
if kw.arg is None:
|
||||||
|
return True
|
||||||
|
if kw.arg != "encoding":
|
||||||
|
continue
|
||||||
|
if isinstance(kw.value, ast.Constant) and kw.value.value in PLATFORM_DEFAULT_ENCODINGS:
|
||||||
|
return False
|
||||||
|
if isinstance(kw.value, ast.Call) and _callee_name(kw.value.func) in PLATFORM_DEFAULT_CALLS:
|
||||||
|
return False # locale.getencoding() is the default, spelled out
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _is_text(call: ast.Call, positional_index: int) -> bool:
|
||||||
|
mode = _mode(call, positional_index)
|
||||||
|
return mode is not UNKNOWN_MODE and "b" not in str(mode)
|
||||||
|
|
||||||
|
|
||||||
|
def _imports_at_each_call(tree: ast.Module) -> dict:
|
||||||
|
"""The imports visible at every call, keyed by node id.
|
||||||
|
|
||||||
|
A function's own imports stay in that function: hoisting them would let one local
|
||||||
|
`from PIL.Image import open` turn off the builtin check for the whole file.
|
||||||
|
"""
|
||||||
|
visible_at = {}
|
||||||
|
|
||||||
|
def walk(node, visible):
|
||||||
|
if isinstance(node, ast.Call):
|
||||||
|
visible_at[id(node)] = visible
|
||||||
|
for child in ast.iter_child_nodes(node):
|
||||||
|
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda)):
|
||||||
|
walk(child, {**visible, **_imported_names(child)})
|
||||||
|
else:
|
||||||
|
walk(child, visible)
|
||||||
|
|
||||||
|
walk(tree, _imported_names(tree))
|
||||||
|
return visible_at
|
||||||
|
|
||||||
|
|
||||||
|
def _foreign_names(tree: ast.Module) -> set:
|
||||||
|
"""Names bound to an object another library built.
|
||||||
|
|
||||||
|
`z = zipfile.ZipFile(p)` then `z.open(name)` is a binary member stream taking no
|
||||||
|
encoding, so demanding one leaves no correct edit.
|
||||||
|
"""
|
||||||
|
modules = _imported_names(tree)
|
||||||
|
names = set()
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if not isinstance(node, ast.Assign) or not isinstance(node.value, ast.Call):
|
||||||
|
continue
|
||||||
|
if _foreign_receiver(node.value, modules):
|
||||||
|
names.update(t.id for t in node.targets if isinstance(t, ast.Name))
|
||||||
|
return names
|
||||||
|
|
||||||
|
|
||||||
|
def _imported_names(tree) -> dict:
|
||||||
|
"""Names this module's imports bind, mapped to where they came from.
|
||||||
|
|
||||||
|
The name alone settles nothing: `import tarfile as tf` hides an opener that takes
|
||||||
|
no encoding, and `from PIL.Image import open` puts another behind the most familiar
|
||||||
|
name there is. Resolving the origin covers both, with no module list to maintain.
|
||||||
|
"""
|
||||||
|
bound = {}
|
||||||
|
stack = list(ast.iter_child_nodes(tree))
|
||||||
|
while stack:
|
||||||
|
node = stack.pop()
|
||||||
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda)):
|
||||||
|
continue # that function's business, not this scope's
|
||||||
|
if isinstance(node, ast.Import):
|
||||||
|
for a in node.names:
|
||||||
|
bound[(a.asname or a.name).split(".")[0]] = a.name
|
||||||
|
elif isinstance(node, ast.ImportFrom):
|
||||||
|
for a in node.names:
|
||||||
|
bound[a.asname or a.name] = f"{node.module}.{a.name}" if node.module else a.name
|
||||||
|
else:
|
||||||
|
stack.extend(ast.iter_child_nodes(node))
|
||||||
|
return bound
|
||||||
|
|
||||||
|
|
||||||
|
def _callee_name(func):
|
||||||
|
"""The bare name a callee ends in, whether or not it is qualified."""
|
||||||
|
return func.id if isinstance(func, ast.Name) else getattr(func, "attr", None)
|
||||||
|
|
||||||
|
|
||||||
|
def _origin_root(name, modules) -> str:
|
||||||
|
"""The top-level module a bound name came from, or the name itself."""
|
||||||
|
return modules.get(name, name).split(".")[0]
|
||||||
|
|
||||||
|
|
||||||
|
def _compressed_key(name, modules):
|
||||||
|
"""The COMPRESSED_OPENERS entry this receiver resolves to, if any."""
|
||||||
|
for candidate in (name, _origin_root(name, modules)):
|
||||||
|
if candidate in COMPRESSED_OPENERS:
|
||||||
|
return candidate
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _open_alias(name, modules):
|
||||||
|
"""What a bare callable resolves to: "builtin", a COMPRESSED_OPENERS key, or None."""
|
||||||
|
origin = modules.get(name)
|
||||||
|
if origin is None:
|
||||||
|
return "builtin" if name == "open" else None
|
||||||
|
parts = origin.split(".")
|
||||||
|
if parts[-1] != "open":
|
||||||
|
return None
|
||||||
|
if parts[0] in BUILTIN_OPEN_MODULES or origin == "open":
|
||||||
|
return "builtin"
|
||||||
|
return parts[0] if parts[0] in COMPRESSED_OPENERS else None
|
||||||
|
|
||||||
|
|
||||||
|
def _is_path_class(name, modules) -> bool:
|
||||||
|
"""True for a pathlib class, including under an alias."""
|
||||||
|
if name is None:
|
||||||
|
return False
|
||||||
|
return (modules.get(name) or name).split(".")[-1] in PATH_CLASSES
|
||||||
|
|
||||||
|
|
||||||
|
def _is_path_attr(node) -> bool:
|
||||||
|
"""True for a qualified path class, as in `pathlib.Path`."""
|
||||||
|
return isinstance(node, ast.Attribute) and node.attr in PATH_CLASSES
|
||||||
|
|
||||||
|
|
||||||
|
def _foreign_receiver(node, modules) -> bool:
|
||||||
|
"""True when the thing before `.open` is an object another library built.
|
||||||
|
|
||||||
|
`zipfile.ZipFile(p).open(name)` returns a binary member stream taking no encoding,
|
||||||
|
so it needs the same exemption as the bare `zipfile.open` spelling.
|
||||||
|
"""
|
||||||
|
if not isinstance(node, ast.Call):
|
||||||
|
return False
|
||||||
|
func = node.func
|
||||||
|
if isinstance(func, ast.Attribute) and isinstance(func.value, ast.Name):
|
||||||
|
root, name = func.value.id, func.attr
|
||||||
|
elif isinstance(func, ast.Name):
|
||||||
|
root = name = func.id
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
return root in modules and not _is_path_class(name, modules)
|
||||||
|
|
||||||
|
|
||||||
|
def _offender(
|
||||||
|
call: ast.Call,
|
||||||
|
modules = None,
|
||||||
|
foreign = (),
|
||||||
|
) -> str | None:
|
||||||
|
"""The call's name if it does text I/O without pinning an encoding."""
|
||||||
|
modules = {} if modules is None else modules
|
||||||
|
func = call.func
|
||||||
|
if isinstance(func, ast.Attribute):
|
||||||
|
receiver = func.value.id if isinstance(func.value, ast.Name) else None
|
||||||
|
# `Path.read_text(p)` is `p.read_text()` unbound: the instance takes slot 0,
|
||||||
|
# so every argument shifts one place right.
|
||||||
|
shift = 1 if _is_path_class(receiver, modules) or _is_path_attr(func.value) else 0
|
||||||
|
if func.attr in GUARDED_METHODS:
|
||||||
|
if func.attr == "read_text" and not shift and call.args:
|
||||||
|
first = call.args[0]
|
||||||
|
# Bound read_text takes encoding first, so None or "locale" there is a
|
||||||
|
# platform-default read. Any other positional means the receiver is
|
||||||
|
# importlib.metadata's Distribution: a filename, and no encoding at all.
|
||||||
|
if isinstance(first, ast.Constant) and first.value in PLATFORM_DEFAULT_ENCODINGS:
|
||||||
|
return "read_text()"
|
||||||
|
return None
|
||||||
|
return None if _names_encoding(call) else f"{func.attr}()"
|
||||||
|
if func.attr == "open":
|
||||||
|
if receiver is not None and _origin_root(receiver, modules) in BUILTIN_OPEN_MODULES:
|
||||||
|
return (
|
||||||
|
None if not _is_text(call, 1) or _names_encoding(call) else f"{receiver}.open()"
|
||||||
|
)
|
||||||
|
compressed = _compressed_key(receiver, modules) if receiver else None
|
||||||
|
if compressed is not None:
|
||||||
|
# "rb" by default, so only an explicit text mode is in scope.
|
||||||
|
mode = _mode(call, 1)
|
||||||
|
if mode is UNKNOWN_MODE or "t" not in str(mode):
|
||||||
|
return None
|
||||||
|
return None if _names_encoding(call) else f"{compressed}.open()"
|
||||||
|
# Any other imported receiver is somebody else's opener: tarfile takes a
|
||||||
|
# compression mode, Image a binary file. Neither has an encoding to name.
|
||||||
|
if receiver is not None and receiver in modules and receiver not in PATH_CLASSES:
|
||||||
|
return None
|
||||||
|
if _foreign_receiver(func.value, modules) or receiver in foreign:
|
||||||
|
return None
|
||||||
|
if not _is_text(call, shift):
|
||||||
|
return None
|
||||||
|
return None if _names_encoding(call) else "Path.open()"
|
||||||
|
return None
|
||||||
|
if isinstance(func, ast.Name):
|
||||||
|
alias = _open_alias(func.id, modules)
|
||||||
|
if alias == "builtin":
|
||||||
|
if not _is_text(call, 1):
|
||||||
|
return None
|
||||||
|
return None if _names_encoding(call) else "open()"
|
||||||
|
if alias is not None:
|
||||||
|
mode = _mode(call, 1)
|
||||||
|
if mode is UNKNOWN_MODE or "t" not in str(mode):
|
||||||
|
return None
|
||||||
|
return None if _names_encoding(call) else f"{alias}.open()"
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _is_test_path(path: Path) -> bool:
|
||||||
|
parts = path.relative_to(REPO).parts
|
||||||
|
if SKIP_DIRS.intersection(parts):
|
||||||
|
return True
|
||||||
|
if "tests" in parts or "test" in parts:
|
||||||
|
return True
|
||||||
|
return path.name.startswith("test_") or path.name.endswith("_test.py")
|
||||||
|
|
||||||
|
|
||||||
|
def _offenders_in(src: str, label: str = "<snippet>"):
|
||||||
|
tree = ast.parse(src, filename = label)
|
||||||
|
visible_at = _imports_at_each_call(tree)
|
||||||
|
foreign = _foreign_names(tree)
|
||||||
|
found = []
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if isinstance(node, ast.Call):
|
||||||
|
name = _offender(node, visible_at.get(id(node), {}), foreign)
|
||||||
|
if name is not None:
|
||||||
|
found.append((node.lineno, name))
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
def _tracked_sources():
|
||||||
|
"""Shipping *.py that git is actually tracking.
|
||||||
|
|
||||||
|
A walk also picks up whatever is lying in the checkout (a built `build/lib` copy,
|
||||||
|
a nested worktree, a vendored dep). None of those are ours to police, and a stale
|
||||||
|
artifact would fail this for everybody who has one.
|
||||||
|
"""
|
||||||
|
listed = subprocess.run(
|
||||||
|
["git", "-C", str(REPO), "ls-files", "-z", "--", "*.py"],
|
||||||
|
capture_output = True,
|
||||||
|
timeout = 60,
|
||||||
|
)
|
||||||
|
if listed.returncode != 0:
|
||||||
|
return None # not a checkout, so fall back to walking
|
||||||
|
names = listed.stdout.decode("utf-8", errors = "replace").split("\0")
|
||||||
|
return [REPO / n for n in names if n]
|
||||||
|
|
||||||
|
|
||||||
|
def _walked_sources():
|
||||||
|
return [p for root in ROOTS if root.is_dir() for p in sorted(root.rglob("*.py"))]
|
||||||
|
|
||||||
|
|
||||||
|
def test_shipping_code_names_an_encoding():
|
||||||
|
offenders = []
|
||||||
|
sources = _tracked_sources()
|
||||||
|
if sources is None:
|
||||||
|
sources = _walked_sources()
|
||||||
|
roots = {r.resolve() for r in ROOTS}
|
||||||
|
for path in sorted(sources):
|
||||||
|
if not roots.intersection(path.resolve().parents) or _is_test_path(path):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
tree = ast.parse(path.read_text(encoding = "utf-8"), filename = str(path))
|
||||||
|
except SyntaxError:
|
||||||
|
continue
|
||||||
|
rel = path.relative_to(REPO).as_posix()
|
||||||
|
visible_at = _imports_at_each_call(tree)
|
||||||
|
foreign = _foreign_names(tree)
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if isinstance(node, ast.Call):
|
||||||
|
name = _offender(node, visible_at.get(id(node), {}), foreign)
|
||||||
|
if name is not None:
|
||||||
|
offenders.append(f"{rel}:{node.lineno}: {name}")
|
||||||
|
assert offenders == [], (
|
||||||
|
f"{len(offenders)} text read/write call sites in shipping code let the "
|
||||||
|
"operator's locale decide the encoding, so they crash or silently "
|
||||||
|
'produce mojibake on Windows. Pass encoding = "utf-8": ' + repr(offenders)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# The assertion above passes vacuously once the trees are clean, so it cannot tell a
|
||||||
|
# working detector from one that always returns None. These pin the detector itself.
|
||||||
|
|
||||||
|
|
||||||
|
def test_detects_the_plain_cases():
|
||||||
|
assert _offenders_in("from pathlib import Path\np = Path('x')\ns = p.read_text()\n")
|
||||||
|
assert _offenders_in("p.write_text('hi')\n")
|
||||||
|
assert _offenders_in("f = open('x')\n")
|
||||||
|
assert _offenders_in("f = open('x', 'w')\n")
|
||||||
|
assert _offenders_in("f = p.open()\n")
|
||||||
|
# Inside a function body too: shipping reads are not import-time.
|
||||||
|
assert _offenders_in("def load(p):\n return p.read_text()\n")
|
||||||
|
|
||||||
|
|
||||||
|
def test_rejects_encoding_that_reselects_the_platform_default():
|
||||||
|
assert _offenders_in("s = p.read_text(encoding = None)\n")
|
||||||
|
assert _offenders_in("s = p.read_text(encoding = 'locale')\n")
|
||||||
|
|
||||||
|
|
||||||
|
def test_accepts_a_pinned_encoding():
|
||||||
|
assert not _offenders_in("s = p.read_text(encoding = 'utf-8')\n")
|
||||||
|
assert not _offenders_in("f = open('x', 'w', encoding = 'utf-8')\n")
|
||||||
|
assert not _offenders_in("f = p.open(encoding = 'utf-8')\n")
|
||||||
|
assert not _offenders_in("s = p.read_text(encoding = 'utf-8', errors = 'replace')\n")
|
||||||
|
|
||||||
|
|
||||||
|
def test_skips_binary_handles():
|
||||||
|
# Binary has no encoding to name; passing one is a ValueError.
|
||||||
|
assert not _offenders_in("f = open('x', 'rb')\n")
|
||||||
|
assert not _offenders_in("f = open('x', mode = 'wb')\n")
|
||||||
|
assert not _offenders_in("f = p.open('rb')\n")
|
||||||
|
|
||||||
|
|
||||||
|
def test_skips_unknown_modes():
|
||||||
|
# A call that may resolve to "rb" has no compliant way to name an encoding.
|
||||||
|
assert not _offenders_in("mode = 'rb' if binary else 'r'\nf = open(path, mode)\n")
|
||||||
|
assert not _offenders_in("f = open(path, mode = chosen)\n")
|
||||||
|
|
||||||
|
|
||||||
|
def test_skips_foreign_openers_and_readers():
|
||||||
|
assert not _offenders_in("import fitz\nd = fitz.open(stream = b, filetype = 'pdf')\n")
|
||||||
|
assert not _offenders_in("import tarfile\nt = tarfile.open(p, 'r:gz')\n")
|
||||||
|
# importlib.metadata Distribution.read_text takes a positional filename.
|
||||||
|
assert not _offenders_in("s = dist.read_text('direct_url.json')\n")
|
||||||
|
|
||||||
|
|
||||||
|
def test_test_trees_are_out_of_scope():
|
||||||
|
assert _is_test_path(REPO / "tests" / "test_x.py")
|
||||||
|
assert _is_test_path(REPO / "studio" / "backend" / "tests" / "helpers.py")
|
||||||
|
assert not _is_test_path(REPO / "studio" / "backend" / "routes" / "inference.py")
|
||||||
|
|
@ -2471,7 +2471,7 @@ def _get_statistics(statistics = None, force_download = True):
|
||||||
for vendor_file in vendor_files:
|
for vendor_file in vendor_files:
|
||||||
path = Path(vendor_file)
|
path = Path(vendor_file)
|
||||||
if path.is_file():
|
if path.is_file():
|
||||||
file_content = path.read_text().lower()
|
file_content = path.read_text(encoding = "utf-8").lower()
|
||||||
if "amazon" in file_content:
|
if "amazon" in file_content:
|
||||||
return "aws"
|
return "aws"
|
||||||
elif "microsoft corporation" in file_content:
|
elif "microsoft corporation" in file_content:
|
||||||
|
|
|
||||||
|
|
@ -1585,7 +1585,7 @@ class FastModel(FastBaseModel):
|
||||||
if do_logging:
|
if do_logging:
|
||||||
redirector = contextlib.nullcontext()
|
redirector = contextlib.nullcontext()
|
||||||
else:
|
else:
|
||||||
redirector = contextlib.redirect_stdout(open(os.devnull, "w"))
|
redirector = contextlib.redirect_stdout(open(os.devnull, "w", encoding = "utf-8"))
|
||||||
|
|
||||||
model_types = ["siglip"] + model_types
|
model_types = ["siglip"] + model_types
|
||||||
# Set forced float32 env flag
|
# Set forced float32 env flag
|
||||||
|
|
|
||||||
|
|
@ -450,7 +450,7 @@ def _load_fp8_weight_map(
|
||||||
index_path = None
|
index_path = None
|
||||||
if index_path is not None:
|
if index_path is not None:
|
||||||
import json
|
import json
|
||||||
with open(index_path, "r") as f:
|
with open(index_path, "r", encoding = "utf-8") as f:
|
||||||
return json.load(f).get("weight_map", None)
|
return json.load(f).get("weight_map", None)
|
||||||
|
|
||||||
# Unsharded single file: map every tensor to it.
|
# Unsharded single file: map every tensor to it.
|
||||||
|
|
|
||||||
|
|
@ -2329,7 +2329,7 @@ def _patch_st_trainer_load_from_checkpoint():
|
||||||
if not os.path.isfile(modules_json):
|
if not os.path.isfile(modules_json):
|
||||||
raise RuntimeError("Unsloth: PEFT checkpoint is missing modules.json.")
|
raise RuntimeError("Unsloth: PEFT checkpoint is missing modules.json.")
|
||||||
try:
|
try:
|
||||||
with open(modules_json, "r") as f:
|
with open(modules_json, "r", encoding = "utf-8") as f:
|
||||||
module_configs = json.load(f)
|
module_configs = json.load(f)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise RuntimeError("Unsloth: Cannot parse checkpoint modules.json.") from e
|
raise RuntimeError("Unsloth: Cannot parse checkpoint modules.json.") from e
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ def _json_rank_count_from_env(name: str) -> Optional[int]:
|
||||||
if value.lstrip().startswith(("[", "{")):
|
if value.lstrip().startswith(("[", "{")):
|
||||||
data = json.loads(value)
|
data = json.loads(value)
|
||||||
else:
|
else:
|
||||||
with open(value, "r") as f:
|
with open(value, "r", encoding = "utf-8") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
except (OSError, json.JSONDecodeError):
|
except (OSError, json.JSONDecodeError):
|
||||||
return None
|
return None
|
||||||
|
|
@ -158,7 +158,7 @@ def quiet_if_nonzero_mlx_rank():
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
saved_stdout_fd = os.dup(1)
|
saved_stdout_fd = os.dup(1)
|
||||||
saved_stderr_fd = os.dup(2)
|
saved_stderr_fd = os.dup(2)
|
||||||
with open(os.devnull, "w") as devnull:
|
with open(os.devnull, "w", encoding = "utf-8") as devnull:
|
||||||
try:
|
try:
|
||||||
os.dup2(devnull.fileno(), 1)
|
os.dup2(devnull.fileno(), 1)
|
||||||
os.dup2(devnull.fileno(), 2)
|
os.dup2(devnull.fileno(), 2)
|
||||||
|
|
|
||||||
|
|
@ -719,7 +719,7 @@ def _cli_update_password(conn: sqlite3.Connection, username: str, new_password:
|
||||||
# credential after a later reset-password deletes auth.db. Mirrors
|
# credential after a later reset-password deletes auth.db. Mirrors
|
||||||
# backend clear_bootstrap_password().
|
# backend clear_bootstrap_password().
|
||||||
try:
|
try:
|
||||||
stale_path.write_text("")
|
stale_path.write_text("", encoding = "utf-8")
|
||||||
cleared = True
|
cleared = True
|
||||||
except OSError:
|
except OSError:
|
||||||
cleared = False
|
cleared = False
|
||||||
|
|
@ -2406,7 +2406,7 @@ def stop():
|
||||||
typer.echo("No running Unsloth server found (no PID file).")
|
typer.echo("No running Unsloth server found (no PID file).")
|
||||||
raise typer.Exit(0)
|
raise typer.Exit(0)
|
||||||
|
|
||||||
pid_text = _PID_FILE.read_text().strip()
|
pid_text = _PID_FILE.read_text(encoding = "utf-8").strip()
|
||||||
if not pid_text.isdigit():
|
if not pid_text.isdigit():
|
||||||
typer.echo(f"Invalid PID file contents: {pid_text}")
|
typer.echo(f"Invalid PID file contents: {pid_text}")
|
||||||
_PID_FILE.unlink(missing_ok = True)
|
_PID_FILE.unlink(missing_ok = True)
|
||||||
|
|
@ -2863,7 +2863,7 @@ def reset_password():
|
||||||
path.unlink(missing_ok = True)
|
path.unlink(missing_ok = True)
|
||||||
except OSError:
|
except OSError:
|
||||||
try:
|
try:
|
||||||
path.write_text("")
|
path.write_text("", encoding = "utf-8")
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
typer.echo(
|
typer.echo(
|
||||||
f"Error: could not remove or clear {path.name} ({exc}); delete "
|
f"Error: could not remove or clear {path.name} ({exc}); delete "
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue