DecisionLab / tests /test_space.py
Michael Stattelman
Added Nano Renemed
abdbb98
Raw History Blame Contribute Delete
6.3 kB
"""The Hugging Face Space packaging: front matter, requirements, entry point and LFS rules."""
import re
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
def front_matter():
text = (ROOT / "README.md").read_text(encoding="utf-8")
m = re.match(r"---\n(.*?)\n---\n", text, re.S)
if not m:
raise AssertionError("README.md must start with YAML front matter for the Space")
out = {}
for line in m.group(1).splitlines():
k, _, v = line.partition(":")
out[k.strip()] = v.strip().strip('"')
return out
def pins(path):
out = {}
for line in (ROOT / path).read_text().splitlines():
line = line.split("#", 1)[0].strip()
if line and not line.startswith("-"):
name, _, version = line.partition("==")
out[re.sub(r"\[.*\]", "", name).lower().replace("_", "-")] = version
return out
@unittest.skipUnless((ROOT / "Dockerfile").is_file(), "packaging files are not in the image")
class SpacePackagingTest(unittest.TestCase):
def test_front_matter_declares_a_gradio_space(self):
fm = front_matter()
self.assertEqual((fm["sdk"], fm["app_file"]), ("gradio", "app.py")) # Hugging Face's default name
self.assertIn(fm["python_version"], ("3.10", "3.12")) # the Pythons ZeroGPU supports
self.assertTrue((ROOT / fm["app_file"]).is_file())
self.assertEqual(fm["title"], "DecisionLab")
def test_sdk_version_matches_the_gradio_pin(self):
self.assertEqual(front_matter()["sdk_version"], pins("requirements.txt")["gradio"])
def test_model_stack_is_pinned_exactly(self):
req = pins("requirements.txt")
for name in ("torch", "transformers", "safetensors", "tokenizers", "huggingface-hub", "laya", "numpy", "gradio"):
with self.subTest(package=name):
self.assertRegex(req.get(name, ""), r"^\d+\.\d+")
def test_torch_is_a_zerogpu_supported_cuda_build(self):
"""ZeroGPU's configuration check (2026-09-28): 2.13.0, 2.12.1, 2.11.0, 2.10.0, 2.9.1, 2.8.0."""
self.assertIn(pins("requirements.txt")["torch"], ("2.13.0", "2.12.1", "2.11.0", "2.10.0", "2.9.1", "2.8.0"))
self.assertNotIn("download.pytorch.org/whl/cpu", (ROOT / "requirements.txt").read_text())
def test_spaces_package_is_required(self):
self.assertIn("spaces", pins("requirements.txt"))
def test_spaces_is_imported_before_anything_else(self):
import ast
tree = ast.parse((ROOT / "app.py").read_text(encoding="utf-8"))
imports = [n for n in tree.body if isinstance(n, (ast.Import, ast.ImportFrom)) and
not (isinstance(n, ast.ImportFrom) and n.module == "__future__")]
first = imports[0]
self.assertEqual(first.names[0].name if isinstance(first, ast.Import) else first.module, "spaces")
def test_web_stack_is_left_to_gradio(self):
req = pins("requirements.txt")
for name in ("fastapi", "starlette", "pydantic", "uvicorn"):
with self.subTest(package=name):
self.assertNotIn(name, req)
def test_model_versions_match_the_container(self):
space, container = pins("requirements.txt"), pins("constraints.txt")
for name in ("transformers", "safetensors", "tokenizers", "huggingface-hub", "laya", "numpy"):
with self.subTest(package=name):
self.assertEqual(space[name], container[name])
def test_weights_go_through_git_lfs(self):
attrs = (ROOT / ".gitattributes").read_text()
for pattern in ("*.safetensors", "tokenizer.json"):
with self.subTest(pattern=pattern):
self.assertRegex(attrs, rf"(?m)^{re.escape(pattern)} filter=lfs diff=lfs merge=lfs -text$")
def test_the_space_starts_through_gradio_launch(self):
"""ZeroGPU's startup step runs inside Gradio's Blocks.launch(); uvicorn + mount_gradio_app skips it
("No @spaces.GPU function detected during startup", 2026-09-30)."""
import ast
tree = ast.parse((ROOT / "app.py").read_text(encoding="utf-8"))
calls = {n.func.attr for n in ast.walk(tree) if isinstance(n, ast.Call) and isinstance(n.func, ast.Attribute)}
imported = {a.name for n in ast.walk(tree) if isinstance(n, ast.Import) for a in n.names}
self.assertIn("launch", calls)
self.assertNotIn("mount_gradio_app", calls)
self.assertNotIn("uvicorn", imported)
def test_no_leftover_space_py(self):
self.assertFalse((ROOT / "space.py").exists())
def test_app_py_imports_the_app_package_not_itself(self):
import subprocess, sys
r = subprocess.run([sys.executable, "-c", "import sys; sys.path.insert(0, '.'); import app.validation; print(app.validation.__file__)"],
cwd=ROOT, capture_output=True, text=True)
self.assertEqual(r.returncode, 0, r.stderr)
self.assertIn("app", r.stdout)
def test_models_folder_exists_for_local_models(self):
self.assertTrue((ROOT / "models").is_dir())
if __name__ == "__main__":
unittest.main()
@unittest.skipUnless((ROOT / "Dockerfile").is_file(), "packaging files are not in the image")
class ModelsFolderReachesTheSpaceTest(unittest.TestCase):
"""A Gradio Space is built with Docker from this repo, so .dockerignore applies to it. Excluding models/ there
kept the Space's local models out of its image (2.2.0, 2026-09-30)."""
def ignored(self):
return [l.strip() for l in (ROOT / ".dockerignore").read_text().splitlines() if l.strip() and not l.startswith("#")]
def test_dockerignore_does_not_exclude_the_models_folder(self):
for pattern in self.ignored():
with self.subTest(pattern=pattern):
self.assertFalse(pattern.rstrip("/").lstrip("./") in ("models", "models/*", "models/**"))
def test_the_container_image_still_leaves_models_out(self):
copies = [l for l in (ROOT / "Dockerfile").read_text().splitlines() if l.startswith(("COPY", "ADD"))]
for line in copies:
with self.subTest(line=line):
self.assertNotRegex(line, r"^(COPY|ADD)\s+(--\S+\s+)*\.\s") # no "COPY . ..."
self.assertNotIn("models", line)