Skip to content
Merged
Show file tree
Hide file tree
Changes from 123 commits
Commits
Show all changes
124 commits
Select commit Hold shift + click to select a range
3a0aaf5
lab-sdk files without lab facade changed
deep1401 Dec 18, 2025
983479b
changes to some routers
deep1401 Dec 18, 2025
251600b
get in most api routers
deep1401 Dec 18, 2025
e598eb5
add more routers logic
deep1401 Dec 18, 2025
8a164b2
fix issues with conversations
deep1401 Dec 18, 2025
f1a1714
shared and services
deep1401 Dec 18, 2025
b16d1a6
format
deep1401 Dec 18, 2025
ab69f8b
plugin sdk and fastchat openai api
deep1401 Dec 18, 2025
f392a41
more plugin sdk
deep1401 Dec 18, 2025
198bfda
fix
deep1401 Dec 18, 2025
0e3222b
individual plugins
deep1401 Dec 18, 2025
516fbc6
imports
deep1401 Dec 18, 2025
b33886b
providers, models, migrations
deep1401 Dec 18, 2025
79a03fe
bug fix
deep1401 Dec 18, 2025
8e64a0e
bug fix
deep1401 Dec 18, 2025
e27af2f
fix
deep1401 Dec 18, 2025
47c4b04
workaround localfs sync
deep1401 Dec 18, 2025
cceb995
storage.open fix
deep1401 Dec 18, 2025
079a7a4
storage.open is smarter and we undo some fixes
deep1401 Dec 18, 2025
d1a56e2
missed await in tasks service
deep1401 Dec 18, 2025
39d83c8
fix problems with plugin install
deep1401 Dec 18, 2025
ad52546
some fixes for download models - more coming
deep1401 Dec 18, 2025
4152adb
fully fix model downloads
deep1401 Dec 18, 2025
7b13786
fix model worker
deep1401 Dec 18, 2025
fd416cb
fix downloading of datasets
deep1401 Dec 18, 2025
0d0d823
some fixes on running train jobs
deep1401 Dec 18, 2025
b41273e
more fixes
deep1401 Dec 18, 2025
96602cb
make training work properly
deep1401 Dec 18, 2025
5b88f37
Change workspace dir imports in all other plugins
deep1401 Dec 19, 2025
e882066
teams fixes
deep1401 Dec 19, 2025
09b93d8
fake async
deep1401 Dec 19, 2025
e3d7a69
fix file reads
deep1401 Dec 19, 2025
6b02de3
bugs bugs
deep1401 Dec 19, 2025
22754a1
Make lab facade have sync and a_<sync> functions
deep1401 Dec 19, 2025
f0c4d51
Merge branch 'main' into add/fsspec-async
deep1401 Dec 19, 2025
de22720
ruff format
deep1401 Dec 19, 2025
1fade0f
Merge branch 'add/fsspec-async' of https://github.com/transformerlab/…
deep1401 Dec 19, 2025
abcb799
fix pat change merging error
deep1401 Dec 19, 2025
b22da80
bump sdk to 60
deep1401 Dec 19, 2025
2bbdc41
security
deep1401 Dec 19, 2025
9ac339d
dependencies update
deep1401 Dec 19, 2025
27f6f82
fix lab sdk tests
deep1401 Dec 19, 2025
ded8074
pytest-asyncio
deep1401 Dec 19, 2025
7399a54
fix
deep1401 Dec 19, 2025
2bd2b83
fix more tests
deep1401 Dec 19, 2025
b559292
fix sdk tests workflow
deep1401 Dec 19, 2025
1efe255
remove pytest disable plugin autoload
deep1401 Dec 19, 2025
5cd2da5
try to fix server tests
deep1401 Dec 19, 2025
1b2937b
fix linux server test too
deep1401 Dec 19, 2025
0912983
fix general pytest too
deep1401 Dec 19, 2025
652929b
dont do it in general pytest
deep1401 Dec 19, 2025
c48cacf
bug fix
deep1401 Dec 19, 2025
b0b91fe
fix all pytests for api with async
deep1401 Dec 19, 2025
45186b7
format
deep1401 Dec 19, 2025
ec91383
fix error related to test/tmp/webapp
deep1401 Dec 19, 2025
7b55c58
increment sdk in api
deep1401 Dec 19, 2025
e8fcb10
codeql
deep1401 Dec 19, 2025
c4457c5
undo all security changes
deep1401 Dec 19, 2025
7c56da0
codeql1
deep1401 Dec 19, 2025
bcdb71b
codeql2
deep1401 Dec 19, 2025
b6f9342
dont have wrong codeql
deep1401 Dec 19, 2025
d495231
more codeql
deep1401 Dec 19, 2025
491df8e
more codeql
deep1401 Dec 19, 2025
59d47fe
reset
deep1401 Dec 19, 2025
45cfa44
retrigger
deep1401 Dec 19, 2025
5b59dc9
Merge branch 'main' into add/fsspec-async
deep1401 Dec 23, 2025
9801136
fix import
deep1401 Dec 23, 2025
801578d
fix merge conflicts on everything -- task template conversion pending
deep1401 Dec 23, 2025
2a01dec
Merge branch 'main' into add/fsspec-async
deep1401 Jan 2, 2026
d88c31b
merge conflicts
deep1401 Jan 7, 2026
503f0b9
Merge branch 'main' into add/fsspec-async
dadmobile Jan 8, 2026
f7d94e7
Update task_template to be async
dadmobile Jan 8, 2026
1b7e6b3
Add missing await
dadmobile Jan 8, 2026
4add9c8
Merge branch 'main' into add/fsspec-async
deep1401 Jan 8, 2026
b1cf4fb
Another missing await
dadmobile Jan 8, 2026
7d98543
Merge branch 'add/fsspec-async' of https://github.com/transformerlab/…
dadmobile Jan 8, 2026
20c751d
some missing awaits
deep1401 Jan 8, 2026
a201073
More awaits
dadmobile Jan 8, 2026
078abb0
functions failing tests
deep1401 Jan 8, 2026
f981198
Merge branch 'add/fsspec-async' of https://github.com/transformerlab/…
deep1401 Jan 8, 2026
158b92a
Update task router to use async
dadmobile Jan 8, 2026
9447624
newer awaits
deep1401 Jan 8, 2026
6829297
Merge branch 'add/fsspec-async' of https://github.com/transformerlab/…
deep1401 Jan 8, 2026
a94d6a7
fix
deep1401 Jan 8, 2026
bc5631b
missed awaits in task
deep1401 Jan 8, 2026
241307f
fix test
deep1401 Jan 8, 2026
aa8093e
fix
deep1401 Jan 8, 2026
9c27088
fix
deep1401 Jan 8, 2026
cde0891
sdk
deep1401 Jan 9, 2026
20b08ad
Merge branch 'main' into add/fsspec-async
deep1401 Jan 9, 2026
3e8b4cb
Missing await in quota calculation code
dadmobile Jan 9, 2026
2aa11eb
fix missing awaits in sweeps and resume checkpoints
deep1401 Jan 9, 2026
eabb4ba
ruff
deep1401 Jan 9, 2026
56da652
Merge branch 'add/fsspec-async' of https://github.com/transformerlab/…
deep1401 Jan 9, 2026
5857100
broken test
deep1401 Jan 9, 2026
894bb69
Merge branch 'main' into add/fsspec-async
dadmobile Jan 9, 2026
7dc1837
remove test as we changed the function
deep1401 Jan 9, 2026
bb19444
Missing await on interactive gallery
dadmobile Jan 9, 2026
3e279a5
Missing await for adaptor path
dadmobile Jan 9, 2026
b11b9ea
fix new function from config multiuser
deep1401 Jan 9, 2026
4c1fec8
Update async dir calls in plugins
dadmobile Jan 9, 2026
8423fc7
Merge branch 'add/fsspec-async' of https://github.com/transformerlab/…
dadmobile Jan 9, 2026
c74ff39
Update async workspace_dir calls
dadmobile Jan 9, 2026
4646115
Missing await
dadmobile Jan 9, 2026
92e60b1
more missed
deep1401 Jan 9, 2026
98d7a1a
Merge branch 'add/fsspec-async' of https://github.com/transformerlab/…
deep1401 Jan 9, 2026
ce0908f
missed test
deep1401 Jan 9, 2026
71cc8d1
import
deep1401 Jan 9, 2026
4bed4af
add more fixes
deep1401 Jan 9, 2026
78306ab
more missed
deep1401 Jan 9, 2026
1519714
turn all a_ functions to async_
deep1401 Jan 9, 2026
9903052
fixed missing get_json_data calls
deep1401 Jan 9, 2026
1e90f0f
fix test
deep1401 Jan 9, 2026
f0e73c4
fixes
deep1401 Jan 9, 2026
2756841
ruff
deep1401 Jan 9, 2026
89d517a
Remove incorrect await
dadmobile Jan 9, 2026
2132353
fix for incorrect worker termination
deep1401 Jan 9, 2026
74d39f1
Merge branch 'add/fsspec-async' of https://github.com/transformerlab/…
deep1401 Jan 9, 2026
96be250
versions in api pyproject
deep1401 Jan 9, 2026
697ffd6
Merge branch 'main' into add/fsspec-async
deep1401 Jan 9, 2026
6bff5a0
update and format all plugin files
deep1401 Jan 9, 2026
6cbbc08
Merge branch 'add/fsspec-async' of https://github.com/transformerlab/…
deep1401 Jan 9, 2026
d75fa39
Revert "update and format all plugin files"
deep1401 Jan 9, 2026
aeaa31a
update plugin versions
deep1401 Jan 9, 2026
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
2 changes: 0 additions & 2 deletions .github/workflows/pytest-sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,5 @@ jobs:
uv pip install -e .

- name: Run SDK tests (uv)
env:
PYTEST_DISABLE_PLUGIN_AUTOLOAD: "1"
run: |
uv run pytest
1 change: 0 additions & 1 deletion .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ jobs:
- name: Test with pytest
run: |
pytest --cov=transformerlab --cov-branch --cov-report=xml -k 'not test_teams'

- name: Upload results to Codecov
uses: codecov/codecov-action@v5
with:
Expand Down
49 changes: 28 additions & 21 deletions api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,28 +104,33 @@
# used internally to set constants that are shared between separate processes. They are not meant to be
# to be overriden by the user.
os.environ["_TFL_SOURCE_CODE_DIR"] = dirs.TFL_SOURCE_CODE_DIR
# The temporary image directory for transformerlab (default; per-request overrides computed in routes)
temp_image_dir = storage.join(get_workspace_dir(), "temp", "images")
os.environ["TLAB_TEMP_IMAGE_DIR"] = str(temp_image_dir)


@asynccontextmanager
async def lifespan(app: FastAPI):
"""Docs on lifespan events: https://fastapi.tiangolo.com/advanced/events/"""
# Do the following at API Startup:
print_launch_message()
# Initialize directories early
from transformerlab.shared import dirs as shared_dirs

await shared_dirs.initialize_dirs()

# Set the temporary image directory for transformerlab (computed async)
temp_image_dir = storage.join(await get_workspace_dir(), "temp", "images")
os.environ["TLAB_TEMP_IMAGE_DIR"] = str(temp_image_dir)
# Validate cloud credentials early - fail fast if missing
validate_cloud_credentials()
galleries.update_gallery_cache()
await galleries.update_gallery_cache()
spawn_fastchat_controller_subprocess()
await db.init() # This now runs Alembic migrations internally
print("✅ SEED DATA")
# Initialize experiments
seed_default_experiments()
await seed_default_experiments()
# Seed default admin user
await seed_default_admin_user()
# Cancel any running jobs
cancel_in_progress_jobs()
await cancel_in_progress_jobs()

# Create buckets for all existing teams if TFL_API_STORAGE_URI is enabled
if os.getenv("TFL_API_STORAGE_URI"):
Expand Down Expand Up @@ -362,7 +367,7 @@ async def server_worker_start(
# then we check to see if we are an experiment
elif experiment_id is not None:
try:
experiment = experiment_get(experiment_id)
experiment = await experiment_get(experiment_id)
experiment_config = (
experiment["config"]
if isinstance(experiment["config"], dict)
Expand Down Expand Up @@ -394,15 +399,15 @@ async def server_worker_start(
model_architecture = model_architecture

plugin_name = inference_engine
plugin_location = lab_dirs.plugin_dir_by_name(plugin_name)
plugin_location = await lab_dirs.plugin_dir_by_name(plugin_name)

model = model_name
if model_filename is not None and model_filename != "":
model = model_filename

if adaptor != "":
# Resolve per-request workspace if multitenant
workspace_dir = get_workspace_dir()
workspace_dir = await get_workspace_dir()
adaptor = f"{workspace_dir}/adaptors/{secure_filename(model)}/{adaptor}"

params = [
Expand All @@ -419,14 +424,14 @@ async def server_worker_start(
json.dumps(inference_params),
]

job_id = job_create(type="LOAD_MODEL", status="STARTED", job_data="{}", experiment_id=experiment_id)
job_id = await job_create(type="LOAD_MODEL", status="STARTED", job_data="{}", experiment_id=experiment_id)

print("Loading plugin loader instead of default worker")

from lab.dirs import get_global_log_path

with storage.open(get_global_log_path(), "a") as global_log:
global_log.write(f"🏃 Loading Inference Server for {model_name} with {inference_params}\n")
async with await storage.open(await get_global_log_path(), "a") as global_log:
await global_log.write(f"🏃 Loading Inference Server for {model_name} with {inference_params}\n")

# Pass organization_id as environment variable to subprocess
from transformerlab.shared.request_context import get_current_org_id
Expand All @@ -447,8 +452,8 @@ async def server_worker_start(
if exitcode == 99:
from lab.dirs import get_global_log_path

with storage.open(get_global_log_path(), "a") as global_log:
global_log.write(
async with await storage.open(await get_global_log_path(), "a") as global_log:
await global_log.write(
"GPU (CUDA) Out of Memory: Please try a smaller model or a different inference engine. Restarting the server may free up resources.\n"
)
return {
Expand All @@ -458,20 +463,20 @@ async def server_worker_start(
if exitcode is not None and exitcode != 0:
from lab.dirs import get_global_log_path

with storage.open(get_global_log_path(), "a") as global_log:
global_log.write(f"Error loading model: {model_name} with exit code {exitcode}\n")
job = job_get(job_id)
async with await storage.open(await get_global_log_path(), "a") as global_log:
await global_log.write(f"Error loading model: {model_name} with exit code {exitcode}\n")
job = await job_get(job_id)
error_msg = None
if job and job.get("job_data"):
error_msg = job["job_data"].get("error_msg")
if not error_msg:
error_msg = f"Exit code {exitcode}"
job_update_status(job_id, "FAILED", experiment_id=experiment_id, error_msg=error_msg)
await job_update_status(job_id, "FAILED", experiment_id=experiment_id, error_msg=error_msg)
return {"status": "error", "message": error_msg}
from lab.dirs import get_global_log_path

with storage.open(get_global_log_path(), "a") as global_log:
global_log.write(f"Model loaded successfully: {model_name}\n")
async with await storage.open(await get_global_log_path(), "a") as global_log:
await global_log.write(f"Model loaded successfully: {model_name}\n")
return {"status": "success", "job_id": job_id}


Expand Down Expand Up @@ -630,7 +635,9 @@ def run():
)

if args.https:
cert_path, key_path = ensure_persistent_self_signed_cert()
import asyncio

cert_path, key_path = asyncio.run(ensure_persistent_self_signed_cert())
uvicorn.run(
"api:app", host=args.host, port=args.port, log_level="warning", ssl_certfile=cert_path, ssl_keyfile=key_path
)
Expand Down
6 changes: 3 additions & 3 deletions api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ dependencies = [
"soundfile==0.13.1",
"tensorboardX==2.6.2.2",
"timm==1.0.15",
"transformerlab==0.0.58",
"transformerlab-inference==0.2.51",
"transformerlab==0.0.62",
"transformerlab-inference==0.2.52",
"transformers==4.57.1",
"wandb==0.19.10",
"werkzeug==3.1.3",
Expand Down Expand Up @@ -109,4 +109,4 @@ cpu = [
"tensorboard==2.18.0",
"tiktoken==0.8.0",
"watchfiles==1.0.4",
]
]
1 change: 1 addition & 0 deletions api/test/api/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

# Create test directories before setting environment variables
os.makedirs("test/tmp/", exist_ok=True)
os.makedirs("test/tmp/webapp", exist_ok=True) # Create webapp directory for static files

os.environ["TFL_HOME_DIR"] = "test/tmp/"
# Note: TFL_WORKSPACE_DIR is not set so that get_workspace_dir() will use the org-based
Expand Down
9 changes: 5 additions & 4 deletions api/test/api/test_data.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest
import os
import json
import asyncio
from io import BytesIO
from PIL import Image
from pathlib import Path
Expand All @@ -13,7 +14,7 @@ def cleanup_dataset(dataset_id, client):
from transformerlab.shared.shared import slugify
import shutil

dataset_dir = dirs.dataset_dir_by_id(slugify(dataset_id))
dataset_dir = asyncio.run(dirs.dataset_dir_by_id(slugify(dataset_id)))
shutil.rmtree(dataset_dir, ignore_errors=True)
client.get(f"/data/delete?dataset_id={dataset_id}")

Expand Down Expand Up @@ -54,7 +55,7 @@ def test_data_info(client):
def test_save_metadata(client):
source_dataset_id = "source_dataset"
new_dataset_id = "destination_dataset"
dataset_dir = dirs.dataset_dir_by_id(slugify(source_dataset_id))
dataset_dir = asyncio.run(dirs.dataset_dir_by_id(slugify(source_dataset_id)))
os.makedirs(dataset_dir, exist_ok=True)

# Create dummy JPEG image
Expand Down Expand Up @@ -96,7 +97,7 @@ def test_save_metadata(client):
data = response.json()
assert data["status"] == "success"

new_dataset_dir = Path(dirs.dataset_dir_by_id(slugify(new_dataset_id)))
new_dataset_dir = Path(asyncio.run(dirs.dataset_dir_by_id(slugify(new_dataset_id))))
assert new_dataset_dir.exists()

cleanup_dataset(source_dataset_id, client)
Expand All @@ -106,7 +107,7 @@ def test_save_metadata(client):
@pytest.mark.skip(reason="Skipping as it contains application-specific logic")
def test_edit_with_template(client):
dataset_id = "test_dataset"
dataset_dir = dirs.dataset_dir_by_id(slugify(dataset_id))
dataset_dir = asyncio.run(dirs.dataset_dir_by_id(slugify(dataset_id)))
os.makedirs(dataset_dir, exist_ok=True)

image_path = os.path.join(dataset_dir, "image.jpg")
Expand Down
17 changes: 11 additions & 6 deletions api/test/api/test_dataset_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ def tmp_dataset_dir(tmp_path: Path) -> Path:
return tmp_path


def test_load_local_dataset_filters_index_and_hidden(tmp_dataset_dir: Path, monkeypatch):
@pytest.mark.asyncio
async def test_load_local_dataset_filters_index_and_hidden(tmp_dataset_dir: Path, monkeypatch):
# Import inside test to ensure module path resolution for monkeypatching
from transformerlab.services import dataset_service

Expand All @@ -30,7 +31,7 @@ def fake_load_dataset(path=None, data_files=None, streaming=False):

monkeypatch.setattr(dataset_service, "load_dataset", fake_load_dataset)

result = dataset_service.load_local_dataset(str(tmp_dataset_dir))
result = await dataset_service.load_local_dataset(str(tmp_dataset_dir))

assert result == {"ok": True}
assert captured["path"] == str(tmp_dataset_dir)
Expand All @@ -43,7 +44,8 @@ def fake_load_dataset(path=None, data_files=None, streaming=False):
assert captured["streaming"] is False


def test_load_local_dataset_uses_explicit_data_files(tmp_path: Path, monkeypatch):
@pytest.mark.asyncio
async def test_load_local_dataset_uses_explicit_data_files(tmp_path: Path, monkeypatch):
from transformerlab.services import dataset_service

# Explicit files provided (note: function should not re-filter these)
Expand All @@ -60,7 +62,9 @@ def fake_load_dataset(path=None, data_files=None, streaming=False):

monkeypatch.setattr(dataset_service, "load_dataset", fake_load_dataset)

result = dataset_service.load_local_dataset(str(tmp_path), data_files=["keep.me", "index.json"], streaming=True)
result = await dataset_service.load_local_dataset(
str(tmp_path), data_files=["keep.me", "index.json"], streaming=True
)

assert result == {"ok": True}
assert captured["path"] == str(tmp_path)
Expand All @@ -72,7 +76,8 @@ def fake_load_dataset(path=None, data_files=None, streaming=False):
assert captured["streaming"] is True


def test_load_local_dataset_fallback_when_no_valid_files(tmp_path: Path, monkeypatch):
@pytest.mark.asyncio
async def test_load_local_dataset_fallback_when_no_valid_files(tmp_path: Path, monkeypatch):
from transformerlab.services import dataset_service

# Only metadata/hidden files present
Expand All @@ -89,7 +94,7 @@ def fake_load_dataset(path=None, data_files=None, streaming=False):

monkeypatch.setattr(dataset_service, "load_dataset", fake_load_dataset)

result = dataset_service.load_local_dataset(str(tmp_path))
result = await dataset_service.load_local_dataset(str(tmp_path))

assert result == {"ok": True}
assert captured["path"] == str(tmp_path)
Expand Down
Loading
Loading