Spaces:
Sleeping
Sleeping
Commit ·
8605269
1
Parent(s): 9d1006c
Boot safely when HF token secret is missing
Browse files- server/config.py +7 -2
- server/storage/hf.py +28 -6
server/config.py
CHANGED
|
@@ -48,10 +48,15 @@ LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
|
|
| 48 |
HF_STORAGE_LABEL = "HF"
|
| 49 |
|
| 50 |
|
| 51 |
-
def
|
| 52 |
value = os.getenv(name, "").strip()
|
| 53 |
if not value and default is not None:
|
| 54 |
value = default.strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
if not value:
|
| 56 |
raise RuntimeError(f"Missing required environment variable: {name}")
|
| 57 |
return value
|
|
@@ -64,7 +69,7 @@ if STORAGE_MODE != HF_STORAGE_LABEL:
|
|
| 64 |
f"DocVault is production-configured for STORAGE_MODE={HF_STORAGE_LABEL} only."
|
| 65 |
)
|
| 66 |
|
| 67 |
-
HF_TOKEN =
|
| 68 |
HF_REPO_ID = _required_env("HF_REPO_ID")
|
| 69 |
HF_REPO_TYPE = _required_env("HF_REPO_TYPE", "dataset")
|
| 70 |
if HF_REPO_TYPE not in {"dataset", "model", "space"}:
|
|
|
|
| 48 |
HF_STORAGE_LABEL = "HF"
|
| 49 |
|
| 50 |
|
| 51 |
+
def _optional_env(name: str, default: str | None = None) -> str | None:
|
| 52 |
value = os.getenv(name, "").strip()
|
| 53 |
if not value and default is not None:
|
| 54 |
value = default.strip()
|
| 55 |
+
return value or None
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def _required_env(name: str, default: str | None = None) -> str:
|
| 59 |
+
value = _optional_env(name, default)
|
| 60 |
if not value:
|
| 61 |
raise RuntimeError(f"Missing required environment variable: {name}")
|
| 62 |
return value
|
|
|
|
| 69 |
f"DocVault is production-configured for STORAGE_MODE={HF_STORAGE_LABEL} only."
|
| 70 |
)
|
| 71 |
|
| 72 |
+
HF_TOKEN = _optional_env("HF_TOKEN") or _optional_env("HUGGING_FACE_HUB_TOKEN") or _optional_env("HF_API_TOKEN")
|
| 73 |
HF_REPO_ID = _required_env("HF_REPO_ID")
|
| 74 |
HF_REPO_TYPE = _required_env("HF_REPO_TYPE", "dataset")
|
| 75 |
if HF_REPO_TYPE not in {"dataset", "model", "space"}:
|
server/storage/hf.py
CHANGED
|
@@ -27,12 +27,22 @@ class HuggingFaceStorageManager(StorageInterface):
|
|
| 27 |
"""Stores all files and folders in a Hugging Face dataset repository."""
|
| 28 |
|
| 29 |
def __init__(self) -> None:
|
| 30 |
-
self.api = HfApi(token=config.HF_TOKEN)
|
| 31 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
def _ensure_repo_exists(self) -> None:
|
|
|
|
| 34 |
try:
|
| 35 |
self.api.repo_info(repo_id=config.HF_REPO_ID, repo_type=config.HF_REPO_TYPE)
|
|
|
|
| 36 |
except Exception:
|
| 37 |
self.api.create_repo(
|
| 38 |
repo_id=config.HF_REPO_ID,
|
|
@@ -40,6 +50,7 @@ class HuggingFaceStorageManager(StorageInterface):
|
|
| 40 |
private=True,
|
| 41 |
exist_ok=True,
|
| 42 |
)
|
|
|
|
| 43 |
|
| 44 |
def _timestamp(self) -> str:
|
| 45 |
return datetime.now(timezone.utc).isoformat()
|
|
@@ -60,10 +71,13 @@ class HuggingFaceStorageManager(StorageInterface):
|
|
| 60 |
return "/".join([repo_folder, config.FOLDER_MARKER]) if repo_folder else config.FOLDER_MARKER
|
| 61 |
|
| 62 |
def _list_repo_files(self) -> List[str]:
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
def _file_exists(self, repo_path: str, repo_files: List[str] | None = None) -> bool:
|
| 69 |
repo_files = repo_files if repo_files is not None else self._list_repo_files()
|
|
@@ -115,6 +129,7 @@ class HuggingFaceStorageManager(StorageInterface):
|
|
| 115 |
counter += 1
|
| 116 |
|
| 117 |
def create_folder(self, user_id: str, folder_path: str) -> Dict[str, Any]:
|
|
|
|
| 118 |
folder_path = self._validate_relative_path(folder_path, "folder_path")
|
| 119 |
if not folder_path:
|
| 120 |
return {"success": False, "error": "folder_path is required", "code": "INVALID_FOLDER"}
|
|
@@ -147,6 +162,7 @@ class HuggingFaceStorageManager(StorageInterface):
|
|
| 147 |
def upload_file(
|
| 148 |
self, user_id: str, folder_path: str, filename: str, file_obj: Any
|
| 149 |
) -> Dict[str, Any]:
|
|
|
|
| 150 |
folder_path = self._validate_relative_path(folder_path, "folder_path")
|
| 151 |
|
| 152 |
relative_path, repo_path = self._next_available_file_path(user_id, folder_path, filename)
|
|
@@ -185,6 +201,7 @@ class HuggingFaceStorageManager(StorageInterface):
|
|
| 185 |
}
|
| 186 |
|
| 187 |
def delete_file(self, user_id: str, file_path: str) -> Dict[str, Any]:
|
|
|
|
| 188 |
file_path = self._validate_relative_path(file_path, "file_path")
|
| 189 |
repo_path = self._user_repo_path(user_id, file_path)
|
| 190 |
if not self._file_exists(repo_path):
|
|
@@ -199,6 +216,7 @@ class HuggingFaceStorageManager(StorageInterface):
|
|
| 199 |
return {"success": True, "message": f"Deleted file: {file_path}"}
|
| 200 |
|
| 201 |
def rename_file(self, user_id: str, file_path: str, new_name: str) -> Dict[str, Any]:
|
|
|
|
| 202 |
file_path = self._validate_relative_path(file_path, "file_path")
|
| 203 |
new_name = sanitize_filename(new_name)
|
| 204 |
if not PathValidator.is_valid_filename(new_name):
|
|
@@ -235,6 +253,7 @@ class HuggingFaceStorageManager(StorageInterface):
|
|
| 235 |
}
|
| 236 |
|
| 237 |
def delete_folder(self, user_id: str, folder_path: str) -> Dict[str, Any]:
|
|
|
|
| 238 |
folder_path = self._validate_relative_path(folder_path, "folder_path")
|
| 239 |
repo_folder_path = self._user_repo_path(user_id, folder_path)
|
| 240 |
repo_files = self._list_repo_files()
|
|
@@ -259,6 +278,7 @@ class HuggingFaceStorageManager(StorageInterface):
|
|
| 259 |
def rename_folder(
|
| 260 |
self, user_id: str, folder_path: str, new_name: str
|
| 261 |
) -> Dict[str, Any]:
|
|
|
|
| 262 |
folder_path = self._validate_relative_path(folder_path, "folder_path")
|
| 263 |
new_name = sanitize_filename(new_name)
|
| 264 |
if not PathValidator.is_valid_filename(new_name):
|
|
@@ -418,6 +438,7 @@ class HuggingFaceStorageManager(StorageInterface):
|
|
| 418 |
}
|
| 419 |
|
| 420 |
def get_history(self, user_id: str, path: str) -> List[Dict[str, Any]]:
|
|
|
|
| 421 |
repo_path = self._user_repo_path(user_id, path)
|
| 422 |
commits = self.api.list_repo_commits(
|
| 423 |
repo_id=config.HF_REPO_ID,
|
|
@@ -441,6 +462,7 @@ class HuggingFaceStorageManager(StorageInterface):
|
|
| 441 |
def restore(
|
| 442 |
self, user_id: str, path: str, revision: str, as_copy: bool = False
|
| 443 |
) -> Dict[str, Any]:
|
|
|
|
| 444 |
path = self._validate_relative_path(path)
|
| 445 |
source_repo_path = self._user_repo_path(user_id, path)
|
| 446 |
destination_repo_path = source_repo_path
|
|
|
|
| 27 |
"""Stores all files and folders in a Hugging Face dataset repository."""
|
| 28 |
|
| 29 |
def __init__(self) -> None:
|
| 30 |
+
self.api = HfApi(token=config.HF_TOKEN) if config.HF_TOKEN else HfApi()
|
| 31 |
+
self._repo_ready = False
|
| 32 |
+
if config.HF_TOKEN:
|
| 33 |
+
self._ensure_repo_exists()
|
| 34 |
+
|
| 35 |
+
def _ensure_token(self) -> None:
|
| 36 |
+
if not config.HF_TOKEN:
|
| 37 |
+
raise RuntimeError(
|
| 38 |
+
"HF_TOKEN is required for write operations. Set HF_TOKEN or HUGGING_FACE_HUB_TOKEN in the Space secrets."
|
| 39 |
+
)
|
| 40 |
|
| 41 |
def _ensure_repo_exists(self) -> None:
|
| 42 |
+
self._ensure_token()
|
| 43 |
try:
|
| 44 |
self.api.repo_info(repo_id=config.HF_REPO_ID, repo_type=config.HF_REPO_TYPE)
|
| 45 |
+
self._repo_ready = True
|
| 46 |
except Exception:
|
| 47 |
self.api.create_repo(
|
| 48 |
repo_id=config.HF_REPO_ID,
|
|
|
|
| 50 |
private=True,
|
| 51 |
exist_ok=True,
|
| 52 |
)
|
| 53 |
+
self._repo_ready = True
|
| 54 |
|
| 55 |
def _timestamp(self) -> str:
|
| 56 |
return datetime.now(timezone.utc).isoformat()
|
|
|
|
| 71 |
return "/".join([repo_folder, config.FOLDER_MARKER]) if repo_folder else config.FOLDER_MARKER
|
| 72 |
|
| 73 |
def _list_repo_files(self) -> List[str]:
|
| 74 |
+
try:
|
| 75 |
+
return self.api.list_repo_files(
|
| 76 |
+
repo_id=config.HF_REPO_ID,
|
| 77 |
+
repo_type=config.HF_REPO_TYPE,
|
| 78 |
+
)
|
| 79 |
+
except Exception:
|
| 80 |
+
return []
|
| 81 |
|
| 82 |
def _file_exists(self, repo_path: str, repo_files: List[str] | None = None) -> bool:
|
| 83 |
repo_files = repo_files if repo_files is not None else self._list_repo_files()
|
|
|
|
| 129 |
counter += 1
|
| 130 |
|
| 131 |
def create_folder(self, user_id: str, folder_path: str) -> Dict[str, Any]:
|
| 132 |
+
self._ensure_token()
|
| 133 |
folder_path = self._validate_relative_path(folder_path, "folder_path")
|
| 134 |
if not folder_path:
|
| 135 |
return {"success": False, "error": "folder_path is required", "code": "INVALID_FOLDER"}
|
|
|
|
| 162 |
def upload_file(
|
| 163 |
self, user_id: str, folder_path: str, filename: str, file_obj: Any
|
| 164 |
) -> Dict[str, Any]:
|
| 165 |
+
self._ensure_token()
|
| 166 |
folder_path = self._validate_relative_path(folder_path, "folder_path")
|
| 167 |
|
| 168 |
relative_path, repo_path = self._next_available_file_path(user_id, folder_path, filename)
|
|
|
|
| 201 |
}
|
| 202 |
|
| 203 |
def delete_file(self, user_id: str, file_path: str) -> Dict[str, Any]:
|
| 204 |
+
self._ensure_token()
|
| 205 |
file_path = self._validate_relative_path(file_path, "file_path")
|
| 206 |
repo_path = self._user_repo_path(user_id, file_path)
|
| 207 |
if not self._file_exists(repo_path):
|
|
|
|
| 216 |
return {"success": True, "message": f"Deleted file: {file_path}"}
|
| 217 |
|
| 218 |
def rename_file(self, user_id: str, file_path: str, new_name: str) -> Dict[str, Any]:
|
| 219 |
+
self._ensure_token()
|
| 220 |
file_path = self._validate_relative_path(file_path, "file_path")
|
| 221 |
new_name = sanitize_filename(new_name)
|
| 222 |
if not PathValidator.is_valid_filename(new_name):
|
|
|
|
| 253 |
}
|
| 254 |
|
| 255 |
def delete_folder(self, user_id: str, folder_path: str) -> Dict[str, Any]:
|
| 256 |
+
self._ensure_token()
|
| 257 |
folder_path = self._validate_relative_path(folder_path, "folder_path")
|
| 258 |
repo_folder_path = self._user_repo_path(user_id, folder_path)
|
| 259 |
repo_files = self._list_repo_files()
|
|
|
|
| 278 |
def rename_folder(
|
| 279 |
self, user_id: str, folder_path: str, new_name: str
|
| 280 |
) -> Dict[str, Any]:
|
| 281 |
+
self._ensure_token()
|
| 282 |
folder_path = self._validate_relative_path(folder_path, "folder_path")
|
| 283 |
new_name = sanitize_filename(new_name)
|
| 284 |
if not PathValidator.is_valid_filename(new_name):
|
|
|
|
| 438 |
}
|
| 439 |
|
| 440 |
def get_history(self, user_id: str, path: str) -> List[Dict[str, Any]]:
|
| 441 |
+
self._ensure_token()
|
| 442 |
repo_path = self._user_repo_path(user_id, path)
|
| 443 |
commits = self.api.list_repo_commits(
|
| 444 |
repo_id=config.HF_REPO_ID,
|
|
|
|
| 462 |
def restore(
|
| 463 |
self, user_id: str, path: str, revision: str, as_copy: bool = False
|
| 464 |
) -> Dict[str, Any]:
|
| 465 |
+
self._ensure_token()
|
| 466 |
path = self._validate_relative_path(path)
|
| 467 |
source_repo_path = self._user_repo_path(user_id, path)
|
| 468 |
destination_repo_path = source_repo_path
|