Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 34 additions & 21 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,27 +175,34 @@ jobs:
- store_artifacts:
path: ~/transformers/installed.txt
- run: make check-repository-consistency
- run:
name: "Test import with all backends (torch + PIL + torchvision)"
command: python -c "from transformers import *" || (echo '🚨 import failed with all backends. Fix unprotected imports!! 🚨'; exit 1)
- run:
name: "Test import with torch only (no PIL, no torchvision)"
command: |
uv pip uninstall Pillow torchvision -q
python -c "from transformers import *" || (echo '🚨 import failed with torch only (no PIL). Fix unprotected imports!! 🚨'; exit 1)
uv pip install -e ".[quality]" -q
- run:
name: "Test import with PIL only (no torch, no torchvision)"
command: |
uv pip uninstall torch torchvision torchaudio -q
python -c "from transformers import *" || (echo '🚨 import failed with PIL only (no torch). Fix unprotected imports!! 🚨'; exit 1)
uv pip install -e ".[quality]" -q
- run:
name: "Test import with torch + PIL, no torchvision"
command: |
uv pip uninstall torchvision -q
python -c "from transformers import *" || (echo '🚨 import failed with torch+PIL but no torchvision. Fix unprotected imports!! 🚨'; exit 1)
uv pip install -e ".[quality]" -q

check_imports:
working_directory: ~/transformers
docker:
- image: huggingface/transformers-consistency
resource_class: large
environment:
TRANSFORMERS_IS_CI: yes
parallelism: 1
steps:
- checkout
- run: apt-get update && apt-get install -y make
- run: uv pip install -e ".[quality]"
- run: make check-imports

check_modular:
working_directory: ~/transformers
docker:
- image: huggingface/transformers-consistency
resource_class: large
environment:
TRANSFORMERS_IS_CI: yes
parallelism: 1
steps:
- checkout
- run: apt-get update && apt-get install -y make
- run: uv pip install -e ".[quality]"
- run: make check-modular


workflows:
Expand All @@ -209,6 +216,8 @@ workflows:
- check_circleci_user
- check_code_quality
- check_repository_consistency
- check_imports
- check_modular
- fetch_tests

setup_and_quality_2:
Expand All @@ -219,6 +228,8 @@ workflows:
- check_circleci_user
- check_code_quality
- check_repository_consistency
- check_imports
- check_modular
- fetch_tests:
# [reference] https://circleci.com/docs/contexts/
context:
Expand All @@ -230,4 +241,6 @@ workflows:
- check_circleci_user
- check_code_quality
- check_repository_consistency
- check_imports
- check_modular
- fetch_all_tests
14 changes: 10 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# make sure to test the local checkout in scripts and not the pre-installed one (don't use quotes!)
export PYTHONPATH = src

.PHONY: style typing check-code-quality check-repository-consistency check-repo fix-repo test test-examples benchmark codex claude clean-ai
.PHONY: style typing check-code-quality check-imports check-modular check-repository-consistency check-repo fix-repo test test-examples benchmark codex claude clean-ai


# Runs all linting/formatting scripts, most notably ruff
Expand Down Expand Up @@ -29,12 +29,18 @@ check-code-quality:
init_isort,\
auto_mappings

# Runs a full repository consistency check.
# Runs import tests under various backend combinations (split out for parallel CI).
check-imports:
@python utils/checkers.py imports

# Runs modular file conversion check (split out for parallel CI).
check-modular:
@python utils/checkers.py modular_conversion

# Runs a full repository consistency check (without imports and modular conversion).
check-repository-consistency:
@python utils/checkers.py \
imports,\
copies,\
modular_conversion,\
doc_toc,\
docstrings,\
dummies,\
Expand Down
66 changes: 57 additions & 9 deletions utils/checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
import shutil
import subprocess
import sys
import tempfile
import threading
import time
from collections import deque
from pathlib import Path

Expand Down Expand Up @@ -195,12 +197,51 @@ def run_deps_table_checker(fix=False, line_callback=None):
return 0, output


# Each scenario: (label, extra packages to install alongside transformers)
_IMPORT_SCENARIOS = [
("all backends (torch + PIL + torchvision)", ["torch", "Pillow", "torchvision"]),
("torch only (no PIL, no torchvision)", ["torch"]),
("PIL only (no torch, no torchvision)", ["Pillow"]),
("torch + PIL, no torchvision", ["torch", "Pillow"]),
]


def run_imports_checker(fix=False, line_callback=None):
"""Check that all public imports work."""
rc, output = _run_cmd([sys.executable, "-c", "from transformers import *"], line_callback=line_callback)
if rc != 0:
return rc, output + "Import failed, this means you introduced unprotected imports!\n"
return 0, output
"""Check that public imports work under various backend combinations.

Each scenario runs in an isolated temporary venv so the caller's
environment is never modified.
"""
all_output: list[str] = []

for label, extras in _IMPORT_SCENARIOS:
with tempfile.TemporaryDirectory() as tmpdir:
venv_dir = os.path.join(tmpdir, ".venv")

# Create venv
rc, out = _run_cmd(["uv", "venv", venv_dir, "--python", sys.executable, "-q"], line_callback=line_callback)
all_output.append(out)
if rc != 0:
all_output.append(f"Failed to create venv for scenario: {label}\n")
return rc, "".join(all_output)

# Install transformers (editable) + the scenario-specific packages
install_cmd = ["uv", "pip", "install", "-e", str(REPO_ROOT), *extras, "-q", "--python", venv_dir]
rc, out = _run_cmd(install_cmd, line_callback=line_callback)
all_output.append(out)
if rc != 0:
all_output.append(f"Failed to install packages for scenario: {label}\n")
return rc, "".join(all_output)

# Run the import test inside the venv
venv_python = os.path.join(venv_dir, "bin", "python")
rc, out = _run_cmd([venv_python, "-c", "from transformers import *"], line_callback=line_callback)
all_output.append(out)
if rc != 0:
all_output.append(f"🚨 Import failed with {label}. Fix unprotected imports!! 🚨\n")
return rc, "".join(all_output)

return 0, "".join(all_output)


RUFF_TARGETS = ["examples", "tests", "src", "utils", "scripts", "benchmark", "benchmark_v2", "setup.py", "conftest.py"]
Expand Down Expand Up @@ -314,6 +355,7 @@ def main():
is_tty = sys.stdout.isatty() and not is_ci

failures = []
total_t0 = time.monotonic()
for name in names:
label = CHECKERS[name][0]
cmd_str = get_checker_command(name, fix=args.fix)
Expand All @@ -322,9 +364,11 @@ def main():
window = SlidingWindow(label, max_lines=10)
if cmd_str:
window.add_line(f"$ {cmd_str}")
t0 = time.monotonic()
rc, output = run_checker(name, fix=args.fix, line_callback=window.add_line)
elapsed = time.monotonic() - t0
window.finish(success=(rc == 0))
print()
print(f" ({elapsed:.1f}s)")
if rc != 0:
failures.append(name)
if not args.keep_going:
Expand All @@ -333,23 +377,27 @@ def main():
print(f"{label}")
if cmd_str:
print(f"$ {cmd_str}")
t0 = time.monotonic()
rc, output = run_checker(name, fix=args.fix)
elapsed = time.monotonic() - t0
tail = output.splitlines()[-10:]
if tail:
print("\n".join(tail))
status = "OK" if rc == 0 else "FAILED"
print(status)
print(f"{status} ({elapsed:.1f}s)")
print()
if rc != 0:
failures.append(name)
if not args.keep_going:
sys.exit(1)

total_elapsed = time.monotonic() - total_t0

if failures:
print(f"\n{len(failures)} failed: {', '.join(failures)}")
print(f"\n{len(failures)} failed: {', '.join(failures)} (total: {total_elapsed:.1f}s)")
sys.exit(1)

print(f"\nAll {len(names)} checks passed.")
print(f"\nAll {len(names)} checks passed. (total: {total_elapsed:.1f}s)")


if __name__ == "__main__":
Expand Down
Loading