[pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
This commit is contained in:
pre-commit-ci[bot] 2026-06-25 16:23:27 +00:00
commit 7b5bb24900
8 changed files with 167 additions and 87 deletions

View file

@ -51,7 +51,9 @@ def resolve_latest_tag(repo: str) -> str:
final_url = response.geturl()
marker = "/releases/tag/"
if marker not in final_url:
raise SystemExit(f"FAIL: could not resolve latest release of {repo} (landed on {final_url})")
raise SystemExit(
f"FAIL: could not resolve latest release of {repo} (landed on {final_url})"
)
return final_url.rsplit(marker, 1)[1].strip("/")

View file

@ -49,14 +49,14 @@ CURATED = [
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--src", required=True, help="Studio 'Sloth emojis' dir")
parser.add_argument("--dest", required=True, help="output dir (static/sloth)")
parser = argparse.ArgumentParser(description = __doc__)
parser.add_argument("--src", required = True, help = "Studio 'Sloth emojis' dir")
parser.add_argument("--dest", required = True, help = "output dir (static/sloth)")
args = parser.parse_args()
os.makedirs(args.dest, exist_ok=True)
os.makedirs(args.dest, exist_ok = True)
installed = 0
for index, name in enumerate(CURATED, start=1):
for index, name in enumerate(CURATED, start = 1):
source = os.path.join(args.src, name)
target = os.path.join(args.dest, "%02d.png" % index)
if not os.path.isfile(source):

View file

@ -38,14 +38,14 @@ def colab_cell_magic_fix(lines):
for i, line in enumerate(lines):
stripped = line.strip()
if stripped == "" or stripped.startswith("#"):
skipped.append(line) # blank or comment (incl. #@title)
skipped.append(line) # blank or comment (incl. #@title)
continue
# First real line. Only act if it is a cell magic that is not yet on
# top (i.e. something was skipped before it).
if stripped.startswith("%%") and i > 0:
return [line] + skipped + lines[i + 1:]
return lines # already on top, or not a magic
return lines # all blank/comment -> nothing to do
return [line] + skipped + lines[i + 1 :]
return lines # already on top, or not a magic
return lines # all blank/comment -> nothing to do
except Exception:
return lines
@ -62,4 +62,4 @@ def register_ipython():
ip.input_transformers_cleanup.append(colab_cell_magic_fix)
ip._unsloth_colab_fix = True
except Exception as e: # never break a kernel because of the helper
print(f"[unsloth-nb] colab-compat hook skipped: {e!r}", file=sys.stderr)
print(f"[unsloth-nb] colab-compat hook skipped: {e!r}", file = sys.stderr)

View file

@ -25,7 +25,6 @@ except Exception as _e: # never break a kernel because of the helper
# failure here never disables the transformers-sidecar hook above and vice versa.
try:
import unsloth_colab_compat
unsloth_colab_compat.register_ipython()
except Exception as _e: # never break a kernel because of the helper
import sys

View file

@ -34,9 +34,14 @@ def _logging_enabled() -> bool:
`[unsloth-nb] activated transformers sidecar ...` line noisy. Set
UNSLOTH_ENABLE_LOGGING=1 to surface it (and other [unsloth-nb] diagnostics)."""
return os.environ.get("UNSLOTH_ENABLE_LOGGING", "").strip().lower() not in (
"", "0", "false", "no", "off",
"",
"0",
"false",
"no",
"off",
)
# Model-name -> minimum transformers tier, ported from Studio's
# transformers_version.py (substring match on the lowered model id). Used as a
# fallback when a notebook does not pin transformers but names a new-family model.

View file

@ -60,9 +60,9 @@ def _strip_lines(lines):
or None if there was nothing to strip."""
for i, line in enumerate(lines):
if line.lstrip().lower().startswith(_INTRO_PREFIX):
out = lines[:i] + lines[i + 1:]
out = lines[:i] + lines[i + 1 :]
if i < len(out) and out[i].strip() == "":
out = out[:i] + out[i + 1:]
out = out[:i] + out[i + 1 :]
return out
return None
@ -77,7 +77,7 @@ def _strip_intro(nb):
return False
src = cell.get("source")
if isinstance(src, str):
lines = src.splitlines(keepends=True)
lines = src.splitlines(keepends = True)
as_str = True
elif isinstance(src, list):
lines = list(src)
@ -104,7 +104,8 @@ def _clean_widgets(nb):
if not isinstance(outs, list):
continue
kept = [
o for o in outs
o
for o in outs
if not (isinstance(o, dict) and _WIDGET_VIEW_MIME in (o.get("data") or {}))
]
if len(kept) != len(outs):
@ -120,7 +121,7 @@ def _clean_widgets(nb):
def strip_notebook(path):
"""Return True if the notebook was modified and written back."""
try:
with open(path, "r", encoding="utf-8") as f:
with open(path, "r", encoding = "utf-8") as f:
nb = json.load(f)
except Exception:
return False
@ -133,8 +134,8 @@ def strip_notebook(path):
tmp = path + ".tmp"
try:
with open(tmp, "w", encoding="utf-8") as f:
json.dump(nb, f, indent=1, ensure_ascii=False)
with open(tmp, "w", encoding = "utf-8") as f:
json.dump(nb, f, indent = 1, ensure_ascii = False)
f.write("\n")
os.replace(tmp, path)
except Exception:
@ -157,7 +158,7 @@ def _sha256(path):
def migrate(state_path, dest):
"""Strip owned+unedited notebooks listed in STATE and update their hashes."""
try:
with open(state_path, "r", encoding="utf-8") as f:
with open(state_path, "r", encoding = "utf-8") as f:
lines = f.read().splitlines()
except OSError:
return 0
@ -165,7 +166,7 @@ def migrate(state_path, dest):
out = []
changed = 0
for line in lines:
parts = line.split(" ", 1) # "<sha256> <relpath>"
parts = line.split(" ", 1) # "<sha256> <relpath>"
if len(parts) != 2:
out.append(line)
continue
@ -173,7 +174,7 @@ def migrate(state_path, dest):
path = os.path.join(dest, rel)
if rel.endswith(".ipynb") and os.path.isfile(path):
try:
if _sha256(path) == rec: # we own it and it is unedited
if _sha256(path) == rec: # we own it and it is unedited
if strip_notebook(path):
rec = _sha256(path)
changed += 1
@ -184,7 +185,7 @@ def migrate(state_path, dest):
if changed:
tmp = state_path + ".tmp"
try:
with open(tmp, "w", encoding="utf-8") as f:
with open(tmp, "w", encoding = "utf-8") as f:
f.write("\n".join(out) + "\n")
os.replace(tmp, state_path)
except OSError:
@ -194,10 +195,10 @@ def migrate(state_path, dest):
def main(argv):
ap = argparse.ArgumentParser(description="Strip the Colab-only intro sentence.")
ap.add_argument("--state", help="sync state file (enables migration mode)")
ap.add_argument("--dest", help="notebooks dir (with --state)")
ap.add_argument("paths", nargs="*", help="notebooks to strip in place")
ap = argparse.ArgumentParser(description = "Strip the Colab-only intro sentence.")
ap.add_argument("--state", help = "sync state file (enables migration mode)")
ap.add_argument("--dest", help = "notebooks dir (with --state)")
ap.add_argument("paths", nargs = "*", help = "notebooks to strip in place")
args = ap.parse_args(argv)
if args.state:

View file

@ -68,11 +68,11 @@ def parse_readme(readme_path):
filename is the urldecoded basename under nb/ (literal parens, matching disk).
"""
with open(readme_path, "r", encoding="utf-8") as f:
with open(readme_path, "r", encoding = "utf-8") as f:
text = f.read()
rows = []
seen_pairs = set() # (section, filename) already emitted
seen_pairs = set() # (section, filename) already emitted
section = None
for line in text.splitlines():
m = re.match(r"^###\s+(.*)$", line)
@ -102,7 +102,11 @@ def _ordered_sections(rows):
return order
def build_view(dest, view, amd=False):
def build_view(
dest,
view,
amd = False,
):
nb_dir = os.path.join(dest, "nb")
readme = os.path.join(dest, "README.md")
if not os.path.isdir(nb_dir):
@ -138,23 +142,23 @@ def build_view(dest, view, amd=False):
# Rebuild VIEW from scratch.
_rmtree(view)
os.makedirs(view, exist_ok=True)
os.makedirs(view, exist_ok = True)
n_links = 0
for i, section in enumerate(order, start=1):
for i, section in enumerate(order, start = 1):
folder = os.path.join(view, f"{i:02d} {section}")
os.makedirs(folder, exist_ok=True)
os.makedirs(folder, exist_ok = True)
for fname in by_section[section]:
link = os.path.join(folder, fname)
target = os.path.join(nb_dir, fname)
rel = os.path.relpath(target, folder) # ../../unsloth-notebooks/nb/<file>
rel = os.path.relpath(target, folder) # ../../unsloth-notebooks/nb/<file>
try:
if os.path.islink(link) or os.path.exists(link):
os.remove(link)
os.symlink(rel, link)
n_links += 1
except OSError as e:
print(f"[unsloth-nb] view: skip {fname}: {e}", file=sys.stderr)
print(f"[unsloth-nb] view: skip {fname}: {e}", file = sys.stderr)
return len(order), n_links
@ -165,7 +169,7 @@ def _rmtree(path):
if os.path.islink(path):
os.remove(path)
return
for root, dirs, files in os.walk(path, topdown=False):
for root, dirs, files in os.walk(path, topdown = False):
for name in files:
try:
os.remove(os.path.join(root, name))
@ -184,12 +188,16 @@ def _rmtree(path):
def main(argv):
ap = argparse.ArgumentParser(description="Build the categorized notebook view.")
ap.add_argument("dest", help="notebooks dir (contains README.md and nb/)")
ap.add_argument("view", nargs="?", help="output view dir (omit with --print)")
ap.add_argument("--amd", action="store_true", help="include AMD-* notebooks")
ap.add_argument("--print", dest="do_print", action="store_true",
help="print section<TAB>file rows instead of building")
ap = argparse.ArgumentParser(description = "Build the categorized notebook view.")
ap.add_argument("dest", help = "notebooks dir (contains README.md and nb/)")
ap.add_argument("view", nargs = "?", help = "output view dir (omit with --print)")
ap.add_argument("--amd", action = "store_true", help = "include AMD-* notebooks")
ap.add_argument(
"--print",
dest = "do_print",
action = "store_true",
help = "print section<TAB>file rows instead of building",
)
args = ap.parse_args(argv)
if args.do_print:
@ -200,7 +208,7 @@ def main(argv):
if not args.view:
ap.error("view dir is required unless --print is given")
n_sections, n_links = build_view(args.dest, args.view, amd=args.amd)
n_sections, n_links = build_view(args.dest, args.view, amd = args.amd)
print(f"[unsloth-nb] view: {n_links} notebooks in {n_sections} folders -> {args.view}")
return 0

View file

@ -11,6 +11,7 @@ login branding fails CI on every device.
Usage: python tests/validate_studio_features.py
Exit 0 = all checks pass; non-zero = at least one failed.
"""
from __future__ import annotations
import importlib
@ -27,7 +28,11 @@ sys.path.insert(0, DOCKER)
_failures: list[str] = []
def check(name: str, cond: bool, detail: str = "") -> None:
def check(
name: str,
cond: bool,
detail: str = "",
) -> None:
status = "PASS" if cond else "FAIL"
print(f" [{status}] {name}" + (f" -- {detail}" if detail and not cond else ""))
if not cond:
@ -40,13 +45,13 @@ def check(name: str, cond: bool, detail: str = "") -> None:
def test_colab_compat() -> None:
print("colab cell-magic compat (unsloth_colab_compat):")
m = importlib.import_module("unsloth_colab_compat")
out = m.colab_cell_magic_fix(['#@title Setup\n', '%%capture\n', '!pip install x\n'])
check("magic hoisted above #@title", out[0] == '%%capture\n' and '#@title Setup\n' in out)
out = m.colab_cell_magic_fix(["#@title Setup\n", "%%capture\n", "!pip install x\n"])
check("magic hoisted above #@title", out[0] == "%%capture\n" and "#@title Setup\n" in out)
# idempotent / already on top
same = ['%%capture\n', 'print(1)\n']
same = ["%%capture\n", "print(1)\n"]
check("no-op when magic already first", m.colab_cell_magic_fix(same) == same)
# non-magic cell untouched
plain = ['x = 1\n', 'y = 2\n']
plain = ["x = 1\n", "y = 2\n"]
check("plain cell untouched", m.colab_cell_magic_fix(plain) == plain)
@ -56,11 +61,16 @@ def test_colab_compat() -> None:
def test_nb_view() -> None:
print("notebook view (unsloth_nb_view):")
v = importlib.import_module("unsloth_nb_view")
check("clean_section dash/slash -> space",
v.clean_section("### GRPO-Reinforcement/Learning Notebooks")
== "GRPO Reinforcement Learning Notebooks",
v.clean_section("### GRPO-Reinforcement/Learning Notebooks"))
check("clean_section strips hashes/space", v.clean_section("## Main Notebooks ") == "Main Notebooks")
check(
"clean_section dash/slash -> space",
v.clean_section("### GRPO-Reinforcement/Learning Notebooks")
== "GRPO Reinforcement Learning Notebooks",
v.clean_section("### GRPO-Reinforcement/Learning Notebooks"),
)
check(
"clean_section strips hashes/space",
v.clean_section("## Main Notebooks ") == "Main Notebooks",
)
# --------------------------------------------------------------------------
@ -72,27 +82,52 @@ def test_strip() -> None:
nb = {
"metadata": {"widgets": {"application/vnd.jupyter.widget-state+json": {"x": 1}}},
"cells": [
{"cell_type": "markdown",
"source": ['To run this, press "Runtime" ... Tesla T4 Google Colab instance!\n',
'\n', 'You will learn how to ...\n']},
{"cell_type": "code", "source": ["print(1)\n"], "outputs": [
{"output_type": "stream", "name": "stdout", "text": "ok\n"},
{"output_type": "display_data",
"data": {"application/vnd.jupyter.widget-view+json": {"model_id": "abc"},
"text/plain": "0%| | 0/10"}},
]},
{
"cell_type": "markdown",
"source": [
'To run this, press "Runtime" ... Tesla T4 Google Colab instance!\n',
"\n",
"You will learn how to ...\n",
],
},
{
"cell_type": "code",
"source": ["print(1)\n"],
"outputs": [
{"output_type": "stream", "name": "stdout", "text": "ok\n"},
{
"output_type": "display_data",
"data": {
"application/vnd.jupyter.widget-view+json": {"model_id": "abc"},
"text/plain": "0%| | 0/10",
},
},
],
},
],
}
changed1 = s._strip_intro(nb)
changed2 = s._clean_widgets(nb)
check("intro line stripped", changed1 and not any(
"to run this, press" in (l.lower()) for l in nb["cells"][0]["source"]))
check(
"intro line stripped",
changed1 and not any("to run this, press" in (l.lower()) for l in nb["cells"][0]["source"]),
)
check("intro body kept", any("You will learn" in l for l in nb["cells"][0]["source"]))
wv = sum(1 for c in nb["cells"] for o in (c.get("outputs", []) or [])
if "application/vnd.jupyter.widget-view+json" in (o.get("data", {}) or {}))
wv = sum(
1
for c in nb["cells"]
for o in (c.get("outputs", []) or [])
if "application/vnd.jupyter.widget-view+json" in (o.get("data", {}) or {})
)
check("widget-view outputs removed", changed2 and wv == 0)
check("non-widget outputs kept", any(
o.get("output_type") == "stream" for c in nb["cells"] for o in (c.get("outputs", []) or [])))
check(
"non-widget outputs kept",
any(
o.get("output_type") == "stream"
for c in nb["cells"]
for o in (c.get("outputs", []) or [])
),
)
check("metadata.widgets removed", "widgets" not in nb["metadata"])
# idempotent
check("strip idempotent", not s._strip_intro(nb) and not s._clean_widgets(nb))
@ -126,21 +161,34 @@ def test_overrides() -> None:
check("overrides.json exists", os.path.isfile(path))
if not os.path.isfile(path):
return
with open(path, encoding="utf-8") as f:
with open(path, encoding = "utf-8") as f:
d = json.load(f) # raises -> CI fails if invalid JSON
themes = d.get("@jupyterlab/apputils-extension:themes", {})
check("default theme = Unsloth Dark", themes.get("theme") == "Unsloth Dark", str(themes.get("theme")))
check(
"default theme = Unsloth Dark",
themes.get("theme") == "Unsloth Dark",
str(themes.get("theme")),
)
check("adaptive theme on", themes.get("adaptive-theme") is True)
check("preferred dark = Unsloth Dark", themes.get("preferred-dark-theme") == "Unsloth Dark")
tracker = d.get("@jupyterlab/notebook-extension:tracker", {})
check("windowingMode none", tracker.get("windowingMode") == "none", str(tracker.get("windowingMode")))
check(
"windowingMode none",
tracker.get("windowingMode") == "none",
str(tracker.get("windowingMode")),
)
notif = d.get("@jupyterlab/apputils-extension:notification", {})
check("news prompt off", str(notif.get("fetchNews")) == "false" and notif.get("checkForUpdates") is False)
check(
"news prompt off",
str(notif.get("fetchNews")) == "false" and notif.get("checkForUpdates") is False,
)
panel = d.get("@jupyterlab/notebook-extension:panel", {})
labels = [t.get("label", "") for t in panel.get("toolbar", [])]
check("Restart & Run All label (single >>)",
any(l == "Restart & Run All" for l in labels) and not any(">>" in l for l in labels),
str(labels))
check(
"Restart & Run All label (single >>)",
any(l == "Restart & Run All" for l in labels) and not any(">>" in l for l in labels),
str(labels),
)
# --------------------------------------------------------------------------
@ -151,7 +199,7 @@ def test_labext_and_branding() -> None:
pkg = os.path.join(LABEXT, "package.json")
check("labext package.json exists", os.path.isfile(pkg))
if os.path.isfile(pkg):
with open(pkg, encoding="utf-8") as f:
with open(pkg, encoding = "utf-8") as f:
p = json.load(f)
check("labext name unsloth-jupyterlab", p.get("name") == "unsloth-jupyterlab")
check("labext themePath set", bool(p.get("jupyterlab", {}).get("themePath")))
@ -162,15 +210,20 @@ def test_labext_and_branding() -> None:
if os.path.isdir(src_dir):
for fn in sorted(os.listdir(src_dir)):
if fn.endswith(".ts"):
with open(os.path.join(src_dir, fn), encoding="utf-8") as f:
with open(os.path.join(src_dir, fn), encoding = "utf-8") as f:
all_src += f.read() + "\n"
for plug in ["unsloth-jupyterlab:theme", "unsloth-jupyterlab:cell-nav",
"unsloth-jupyterlab:logo", "unsloth-jupyterlab:colab-title",
"unsloth-jupyterlab:output-select-all", "unsloth-jupyterlab:ui-chrome"]:
for plug in [
"unsloth-jupyterlab:theme",
"unsloth-jupyterlab:cell-nav",
"unsloth-jupyterlab:logo",
"unsloth-jupyterlab:colab-title",
"unsloth-jupyterlab:output-select-all",
"unsloth-jupyterlab:ui-chrome",
]:
check(f"plugin present: {plug}", plug in all_src)
# The two newest plugins are also exported from index.ts (wired in).
index = os.path.join(src_dir, "index.ts")
index_src = open(index, encoding="utf-8").read() if os.path.isfile(index) else ""
index_src = open(index, encoding = "utf-8").read() if os.path.isfile(index) else ""
check("outputSelect wired in index.ts", "outputSelectPlugin" in index_src)
check("uiChrome wired in index.ts", "uiChromePlugin" in index_src)
# uiChrome hides the right activity bar; CTRL+A output-select selects nodes.
@ -178,18 +231,30 @@ def test_labext_and_branding() -> None:
check("ctrl+A output select", "selectNodeContents" in all_src)
# branding assets
login = os.path.join(JUPYTER, "login.html")
login_src = open(login, encoding="utf-8").read() if os.path.isfile(login) else ""
login_src = open(login, encoding = "utf-8").read() if os.path.isfile(login) else ""
check("login.html branded", "unsloth-login-card" in login_src)
check("login.html uses sloth stickers", 'static_url("sloth/' in login_src or "static_url('sloth/" in login_src)
check(
"login.html uses sloth stickers",
'static_url("sloth/' in login_src or "static_url('sloth/" in login_src,
)
check("favicon.ico present", os.path.isfile(os.path.join(JUPYTER, "favicon.ico")))
check("logo.png present", os.path.isfile(os.path.join(JUPYTER, "logo.png")))
check("sloth sticker installer present", os.path.isfile(os.path.join(JUPYTER, "install_sloth_stickers.py")))
check(
"sloth sticker installer present",
os.path.isfile(os.path.join(JUPYTER, "install_sloth_stickers.py")),
)
def main() -> int:
print("=== Unsloth Studio/notebook feature validation ===")
for t in (test_colab_compat, test_nb_view, test_strip, test_sidecar_log_gate,
test_overrides, test_labext_and_branding):
for t in (
test_colab_compat,
test_nb_view,
test_strip,
test_sidecar_log_gate,
test_overrides,
test_labext_and_branding,
):
try:
t()
except Exception as e: # a thrown exception is a failure, not a crash