Spaces:
Running
Fix React and Streamlit deployment: Upload files individually like transformers.js
Browse filesIssue: React and Streamlit apps were using upload_folder() which doesn't match
the original deploy.py behavior, potentially causing issues with multi-file apps.
Root cause analysis from original deploy.py:
- Transformers.js: Individual file uploads ✅ (already fixed)
- React: Individual file uploads ❌ (was using upload_folder)
- Streamlit: Individual file uploads ❌ (was using upload_folder)
- HTML (static): upload_folder() for multi-file OR upload_file() for single
- Gradio: create_commit() with atomic ops for multi-file OR upload_file()
Changes to backend_deploy.py:
1. React deployment now uses individual uploads:
- Set use_individual_uploads = True for React apps
- Parses React output to get all files
- Uploads each file individually with retry logic
- Includes Dockerfile if missing (from template)
2. Streamlit deployment now uses individual uploads:
- Set use_individual_uploads = True for Streamlit (Docker mode)
- Uploads each file individually with retry logic
- Includes Dockerfile and requirements.txt
3. Enhanced subdirectory support:
- All file writes now create parent directories if needed
- Handles nested structures like pages/, components/, styles/
- Path handling works correctly on Windows and Unix
4. Improved individual upload logic:
- Dynamically scans temp directory for all files
- Handles subdirectories with proper path separators
- Converts backslashes to forward slashes for repo paths
- Works for any number of files (not hardcoded list)
5. Better logging:
- Shows how many files are being uploaded
- Lists all file paths being uploaded
- Per-file success messages
- Retry attempt logging
Upload behavior by language:
- transformers.js: Individual uploads (index.html, index.js, style.css)
- React: Individual uploads (all parsed files + Dockerfile)
- Streamlit: Individual uploads (all parsed files + Dockerfile + requirements.txt)
- HTML: upload_folder() (works fine for static sites)
- Gradio: upload_folder() (works fine for Gradio SDK)
- ComfyUI: upload_folder() (works fine for static)
This ensures React and Streamlit apps with multiple files, nested directories,
and Dockerfiles deploy correctly, matching the original deploy.py behavior.
- backend_deploy.py +41 -12
|
@@ -348,9 +348,11 @@ def deploy_to_huggingface_space(
|
|
| 348 |
elif language in ["gradio", "streamlit"]:
|
| 349 |
files = parse_multi_file_python_output(code)
|
| 350 |
|
| 351 |
-
# Write Python files
|
| 352 |
for filename, content in files.items():
|
| 353 |
-
|
|
|
|
|
|
|
| 354 |
|
| 355 |
# Ensure requirements.txt exists
|
| 356 |
if "requirements.txt" not in files:
|
|
@@ -365,26 +367,38 @@ def deploy_to_huggingface_space(
|
|
| 365 |
dockerfile = create_dockerfile_for_streamlit(space_name)
|
| 366 |
(temp_path / "Dockerfile").write_text(dockerfile, encoding='utf-8')
|
| 367 |
app_port = 7860 # Set app_port for Docker spaces
|
|
|
|
| 368 |
|
| 369 |
elif language == "react":
|
| 370 |
-
#
|
| 371 |
-
# This is more complex, so for now just create a placeholder
|
| 372 |
files = parse_multi_file_python_output(code)
|
| 373 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
for filename, content in files.items():
|
| 375 |
-
|
|
|
|
|
|
|
| 376 |
|
| 377 |
-
# Create Dockerfile
|
| 378 |
-
dockerfile = create_dockerfile_for_react(space_name)
|
| 379 |
-
(temp_path / "Dockerfile").write_text(dockerfile, encoding='utf-8')
|
| 380 |
app_port = 7860 # Set app_port for Docker spaces
|
|
|
|
| 381 |
|
| 382 |
else:
|
| 383 |
# Default: treat as Gradio app
|
| 384 |
files = parse_multi_file_python_output(code)
|
| 385 |
|
|
|
|
| 386 |
for filename, content in files.items():
|
| 387 |
-
|
|
|
|
|
|
|
| 388 |
|
| 389 |
if "requirements.txt" not in files:
|
| 390 |
(temp_path / "requirements.txt").write_text("gradio>=4.0.0\n", encoding='utf-8')
|
|
@@ -438,13 +452,26 @@ def deploy_to_huggingface_space(
|
|
| 438 |
|
| 439 |
try:
|
| 440 |
if use_individual_uploads:
|
| 441 |
-
# For transformers.js, upload each file individually (matches original deploy.py)
|
| 442 |
import time
|
| 443 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 444 |
|
| 445 |
max_attempts = 3
|
| 446 |
for filename in files_to_upload:
|
| 447 |
-
|
|
|
|
| 448 |
if not file_path.exists():
|
| 449 |
return False, f"Failed to upload: {filename} not found", None
|
| 450 |
|
|
@@ -462,6 +489,7 @@ def deploy_to_huggingface_space(
|
|
| 462 |
commit_message=f"{commit_message} - {filename}"
|
| 463 |
)
|
| 464 |
success = True
|
|
|
|
| 465 |
break
|
| 466 |
except Exception as e:
|
| 467 |
last_error = e
|
|
@@ -469,6 +497,7 @@ def deploy_to_huggingface_space(
|
|
| 469 |
return False, f"Permission denied uploading {filename}. Check your token has write access.", None
|
| 470 |
if attempt < max_attempts - 1:
|
| 471 |
time.sleep(2) # Wait before retry
|
|
|
|
| 472 |
|
| 473 |
if not success:
|
| 474 |
return False, f"Failed to upload {filename} after {max_attempts} attempts: {last_error}", None
|
|
|
|
| 348 |
elif language in ["gradio", "streamlit"]:
|
| 349 |
files = parse_multi_file_python_output(code)
|
| 350 |
|
| 351 |
+
# Write Python files (create subdirectories if needed)
|
| 352 |
for filename, content in files.items():
|
| 353 |
+
file_path = temp_path / filename
|
| 354 |
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
| 355 |
+
file_path.write_text(content, encoding='utf-8')
|
| 356 |
|
| 357 |
# Ensure requirements.txt exists
|
| 358 |
if "requirements.txt" not in files:
|
|
|
|
| 367 |
dockerfile = create_dockerfile_for_streamlit(space_name)
|
| 368 |
(temp_path / "Dockerfile").write_text(dockerfile, encoding='utf-8')
|
| 369 |
app_port = 7860 # Set app_port for Docker spaces
|
| 370 |
+
use_individual_uploads = True # Streamlit uses individual file uploads
|
| 371 |
|
| 372 |
elif language == "react":
|
| 373 |
+
# Parse React output to get all files (uses same multi-file format as Python)
|
|
|
|
| 374 |
files = parse_multi_file_python_output(code)
|
| 375 |
|
| 376 |
+
if not files:
|
| 377 |
+
return False, "Error: Could not parse React output", None
|
| 378 |
+
|
| 379 |
+
# If Dockerfile is missing, use template
|
| 380 |
+
if 'Dockerfile' not in files:
|
| 381 |
+
dockerfile = create_dockerfile_for_react(space_name)
|
| 382 |
+
files['Dockerfile'] = dockerfile
|
| 383 |
+
|
| 384 |
+
# Write all React files (create subdirectories if needed)
|
| 385 |
for filename, content in files.items():
|
| 386 |
+
file_path = temp_path / filename
|
| 387 |
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
| 388 |
+
file_path.write_text(content, encoding='utf-8')
|
| 389 |
|
|
|
|
|
|
|
|
|
|
| 390 |
app_port = 7860 # Set app_port for Docker spaces
|
| 391 |
+
use_individual_uploads = True # React uses individual file uploads
|
| 392 |
|
| 393 |
else:
|
| 394 |
# Default: treat as Gradio app
|
| 395 |
files = parse_multi_file_python_output(code)
|
| 396 |
|
| 397 |
+
# Write files (create subdirectories if needed)
|
| 398 |
for filename, content in files.items():
|
| 399 |
+
file_path = temp_path / filename
|
| 400 |
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
| 401 |
+
file_path.write_text(content, encoding='utf-8')
|
| 402 |
|
| 403 |
if "requirements.txt" not in files:
|
| 404 |
(temp_path / "requirements.txt").write_text("gradio>=4.0.0\n", encoding='utf-8')
|
|
|
|
| 452 |
|
| 453 |
try:
|
| 454 |
if use_individual_uploads:
|
| 455 |
+
# For transformers.js, React, Streamlit: upload each file individually (matches original deploy.py)
|
| 456 |
import time
|
| 457 |
+
|
| 458 |
+
# Get list of files to upload from temp directory
|
| 459 |
+
files_to_upload = []
|
| 460 |
+
for file_path in temp_path.rglob('*'):
|
| 461 |
+
if file_path.is_file():
|
| 462 |
+
# Get relative path from temp directory (use forward slashes for repo paths)
|
| 463 |
+
rel_path = file_path.relative_to(temp_path)
|
| 464 |
+
files_to_upload.append(str(rel_path).replace('\\', '/'))
|
| 465 |
+
|
| 466 |
+
if not files_to_upload:
|
| 467 |
+
return False, "No files to upload", None
|
| 468 |
+
|
| 469 |
+
print(f"[Deploy] Uploading {len(files_to_upload)} files individually: {files_to_upload}")
|
| 470 |
|
| 471 |
max_attempts = 3
|
| 472 |
for filename in files_to_upload:
|
| 473 |
+
# Convert back to Path for filesystem operations
|
| 474 |
+
file_path = temp_path / filename.replace('/', os.sep)
|
| 475 |
if not file_path.exists():
|
| 476 |
return False, f"Failed to upload: {filename} not found", None
|
| 477 |
|
|
|
|
| 489 |
commit_message=f"{commit_message} - {filename}"
|
| 490 |
)
|
| 491 |
success = True
|
| 492 |
+
print(f"[Deploy] Successfully uploaded {filename}")
|
| 493 |
break
|
| 494 |
except Exception as e:
|
| 495 |
last_error = e
|
|
|
|
| 497 |
return False, f"Permission denied uploading {filename}. Check your token has write access.", None
|
| 498 |
if attempt < max_attempts - 1:
|
| 499 |
time.sleep(2) # Wait before retry
|
| 500 |
+
print(f"[Deploy] Retry {attempt + 1}/{max_attempts} for {filename}")
|
| 501 |
|
| 502 |
if not success:
|
| 503 |
return False, f"Failed to upload {filename} after {max_attempts} attempts: {last_error}", None
|