from __future__ import annotations from pathlib import Path import tempfile def studio_root() -> Path: return Path.home() / ".unsloth" / "studio" def assets_root() -> Path: return studio_root() / "assets" def datasets_root() -> Path: return assets_root() / "datasets" def dataset_uploads_root() -> Path: return datasets_root() / "uploads" def recipe_datasets_root() -> Path: return datasets_root() / "recipes" def outputs_root() -> Path: return studio_root() / "outputs" def exports_root() -> Path: return studio_root() / "exports" def auth_root() -> Path: return studio_root() / "auth" def auth_db_path() -> Path: return auth_root() / "auth.db" def tmp_root() -> Path: return Path(tempfile.gettempdir()) / "unsloth-studio" def seed_uploads_root() -> Path: return tmp_root() / "seed-uploads" def unstructured_seed_cache_root() -> Path: return tmp_root() / "unstructured-seed-cache" def oxc_validator_tmp_root() -> Path: return tmp_root() / "oxc-validator" def tensorboard_root() -> Path: return studio_root() / "runs" def ensure_dir(path: Path) -> Path: path.mkdir(parents=True, exist_ok=True) return path def _clean_relative_path(path_value: str, *, strip_prefixes: tuple[str, ...] = ()) -> Path: path = Path(path_value).expanduser() parts = [part for part in path.parts if part not in ("", ".")] while parts and parts[0] in strip_prefixes: parts = parts[1:] return Path(*parts) if parts else Path() def resolve_under_root( path_value: str | None, *, root: Path, strip_prefixes: tuple[str, ...] = (), ) -> Path: if not path_value or not str(path_value).strip(): return root path = Path(str(path_value).strip()).expanduser() if path.is_absolute(): return path cleaned = _clean_relative_path(str(path), strip_prefixes=strip_prefixes) return root / cleaned def resolve_output_dir(path_value: str | None = None) -> Path: return resolve_under_root( path_value, root=outputs_root(), strip_prefixes=("outputs",), ) def resolve_export_dir(path_value: str | None = None) -> Path: return resolve_under_root( path_value, root=exports_root(), strip_prefixes=("exports",), ) def resolve_tensorboard_dir(path_value: str | None = None) -> Path: return resolve_under_root( path_value, root=tensorboard_root(), strip_prefixes=("runs", "tensorboard"), ) def resolve_dataset_path(path_value: str) -> Path: path = Path(path_value).expanduser() if path.is_absolute(): return path parts = [part for part in Path(path_value).parts if part not in ("", ".")] if parts[:2] == ["assets", "datasets"]: parts = parts[2:] if parts and parts[0] == "uploads": cleaned = Path(*parts[1:]) if len(parts) > 1 else Path() return dataset_uploads_root() / cleaned if parts and parts[0] == "recipes": cleaned = Path(*parts[1:]) if len(parts) > 1 else Path() return recipe_datasets_root() / cleaned cleaned = Path(*parts) if parts else Path() candidates = [ dataset_uploads_root() / cleaned, recipe_datasets_root() / cleaned, datasets_root() / cleaned, dataset_uploads_root() / cleaned.name, recipe_datasets_root() / cleaned.name, ] for candidate in candidates: if candidate.exists(): return candidate return candidates[0]