akhaliq HF Staff commited on
Commit
a47f768
·
1 Parent(s): 1e9598c

Fix React and Streamlit deployment: Upload files individually like transformers.js

Browse files

Issue: 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.

Files changed (1) hide show
  1. backend_deploy.py +41 -12
backend_deploy.py CHANGED
@@ -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
- (temp_path / filename).write_text(content, encoding='utf-8')
 
 
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
- # For React, we'd need package.json and other files
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
- (temp_path / filename).write_text(content, encoding='utf-8')
 
 
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
- (temp_path / filename).write_text(content, encoding='utf-8')
 
 
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
- files_to_upload = ["index.html", "index.js", "style.css"]
 
 
 
 
 
 
 
 
 
 
 
 
444
 
445
  max_attempts = 3
446
  for filename in files_to_upload:
447
- file_path = temp_path / filename
 
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