broadfield-dev commited on
Commit
dea3ee4
·
verified ·
1 Parent(s): aee4cb7

Update build_logic.py

Browse files
Files changed (1) hide show
  1. build_logic.py +138 -26
build_logic.py CHANGED
@@ -12,7 +12,9 @@ from huggingface_hub import (
12
  whoami,
13
  hf_hub_download,
14
  delete_file as hf_delete_file,
15
- HfApi
 
 
16
  )
17
  from huggingface_hub.hf_api import CommitOperationDelete, CommitOperationAdd, CommitOperation
18
  from huggingface_hub.utils import HfHubHTTPError
@@ -311,6 +313,47 @@ def apply_staged_changes(ui_api_token_from_textbox, owner_ui, space_name_ui, cha
311
 
312
  api = HfApi(token=resolved_api_token)
313
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  create_space_op = next((c for c in changeset if c['type'] == 'CREATE_SPACE'), None)
315
  if create_space_op:
316
  try:
@@ -450,31 +493,7 @@ def apply_staged_changes(ui_api_token_from_textbox, owner_ui, space_name_ui, cha
450
  status_messages.append(f"SET_PRIVACY Error: {str(e)}. Check logs.")
451
  logger.exception(f"Error setting privacy for {target_repo_id}:")
452
 
453
- elif change['type'] == 'DELETE_SPACE':
454
- delete_owner = change.get('owner') or owner_ui
455
- delete_space = change.get('space_name') or space_name_ui
456
- delete_repo_id = f"{delete_owner}/{delete_space}" if delete_owner and delete_space else repo_id
457
-
458
- if not delete_repo_id:
459
- status_messages.append("DELETE_SPACE Error: Target repo_id not specified.")
460
- continue
461
-
462
- if delete_repo_id != repo_id:
463
- status_messages.append(f"DELETE_SPACE Error: AI requested deletion of '{delete_repo_id}', but this action is only permitted for the currently loaded space '{repo_id}'. Action blocked.")
464
- logger.warning(f"Blocked DELETE_SPACE action: requested '{delete_repo_id}', current '{repo_id}'.")
465
- continue
466
-
467
- logger.warning(f"Attempting DESTRUCTIVE DELETE_SPACE action for {delete_repo_id}")
468
- try:
469
- api.delete_repo(repo_id=delete_repo_id, repo_type='space')
470
- status_messages.append(f"DELETE_SPACE: Successfully deleted space `{delete_repo_id}`.")
471
- logger.warning(f"Successfully deleted space {delete_repo_id}.")
472
- except HfHubHTTPError as e_http:
473
- status_messages.append(f"DELETE_SPACE HTTP Error ({e_http.response.status_code if e_http.response else 'N/A'}): {e_http.response.text if e_http.response else str(e_http)}. Check token/permissions.")
474
- logger.error(f"HTTP error deleting space {delete_repo_id}: {e_http}")
475
- except Exception as e:
476
- status_messages.append(f"DELETE_SPACE Error: {str(e)}. Check logs.")
477
- logger.exception(f"Error deleting space {delete_repo_id}:")
478
 
479
  except HfHubHTTPError as e_http:
480
  logger.error(f"Top-level HTTP error during apply_staged_changes for {repo_id_for_error_logging or 'unknown repo'}: {e_http}")
@@ -680,3 +699,96 @@ def build_logic_delete_space(hf_api_key, owner, space_name):
680
  except Exception as e:
681
  logger.exception(f"Error deleting space {repo_id}:")
682
  return f"Error deleting space `{repo_id}`: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  whoami,
13
  hf_hub_download,
14
  delete_file as hf_delete_file,
15
+ HfApi,
16
+ duplicate_repo as hf_duplicate_repo,
17
+ list_repos as hf_list_repos
18
  )
19
  from huggingface_hub.hf_api import CommitOperationDelete, CommitOperationAdd, CommitOperation
20
  from huggingface_hub.utils import HfHubHTTPError
 
313
 
314
  api = HfApi(token=resolved_api_token)
315
 
316
+ # --- Handle Exclusive Actions First ---
317
+ exclusive_action = next((c for c in changeset if c['type'] in ['DUPLICATE_SPACE', 'DELETE_SPACE']), None)
318
+
319
+ if exclusive_action:
320
+ if exclusive_action['type'] == 'DUPLICATE_SPACE':
321
+ # This should be handled in the confirm_changes handler to trigger a space load
322
+ # Reaching here means the logic in confirm_changes failed to intercept it
323
+ status_messages.append("Internal Error: DUPLICATE_SPACE action should have been handled exclusively.")
324
+ logger.error("Internal Error: DUPLICATE_SPACE action was passed to apply_staged_changes unexpectedly.")
325
+ elif exclusive_action['type'] == 'DELETE_SPACE':
326
+ # This should also ideally be handled by confirm_changes for UI state reset, but we can execute it here too
327
+ delete_owner = exclusive_action.get('owner') or owner_ui
328
+ delete_space = exclusive_action.get('space_name') or space_name_ui
329
+ delete_repo_id_target = f"{delete_owner}/{delete_space}" if delete_owner and delete_space else repo_id
330
+
331
+ if not delete_repo_id_target:
332
+ status_messages.append("DELETE_SPACE Error: Target repo_id not specified.")
333
+ elif delete_repo_id_target != repo_id:
334
+ status_messages.append(f"DELETE_SPACE Error: AI requested deletion of '{delete_repo_id_target}', but this action is only permitted for the currently loaded space '{repo_id}'. Action blocked.")
335
+ logger.warning(f"Blocked DELETE_SPACE action in apply_staged_changes: requested '{delete_repo_id_target}', current '{repo_id}'.")
336
+ else:
337
+ logger.warning(f"Attempting DESTRUCTIVE DELETE_SPACE action for {delete_repo_id_target}")
338
+ try:
339
+ api.delete_repo(repo_id=delete_repo_id_target, repo_type='space')
340
+ status_messages.append(f"DELETE_SPACE: Successfully deleted space `{delete_repo_id_target}`.")
341
+ logger.warning(f"Successfully deleted space {delete_repo_id_target}.")
342
+ except HfHubHTTPError as e_http:
343
+ status_messages.append(f"DELETE_SPACE HTTP Error ({e_http.response.status_code if e_http.response else 'N/A'}): {e_http.response.text if e_http.response else str(e_http)}. Check token/permissions.")
344
+ logger.error(f"HTTP error deleting space {delete_repo_id_target}: {e_http}")
345
+ except Exception as e:
346
+ status_messages.append(f"DELETE_SPACE Error: {str(e)}. Check logs.")
347
+ logger.exception(f"Error deleting space {delete_repo_id_target}:")
348
+ # If an exclusive action was found and potentially processed, stop here
349
+ final_status = " | ".join(status_messages) if status_messages else "Exclusive operation attempted."
350
+ logger.info(f"Exclusive action processed. Final status: {final_status}")
351
+ return final_status
352
+
353
+
354
+ # --- Handle Non-Exclusive Actions and File Changes ---
355
+ # This block is only reached if no exclusive action was found
356
+
357
  create_space_op = next((c for c in changeset if c['type'] == 'CREATE_SPACE'), None)
358
  if create_space_op:
359
  try:
 
493
  status_messages.append(f"SET_PRIVACY Error: {str(e)}. Check logs.")
494
  logger.exception(f"Error setting privacy for {target_repo_id}:")
495
 
496
+ # Note: DELETE_SPACE and DUPLICATE_SPACE are handled as exclusive actions at the top
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
 
498
  except HfHubHTTPError as e_http:
499
  logger.error(f"Top-level HTTP error during apply_staged_changes for {repo_id_for_error_logging or 'unknown repo'}: {e_http}")
 
699
  except Exception as e:
700
  logger.exception(f"Error deleting space {repo_id}:")
701
  return f"Error deleting space `{repo_id}`: {e}"
702
+
703
+ def duplicate_space(hf_api_key, source_repo_id, target_repo_id, private: bool = False):
704
+ """Duplicates a Hugging Face Space."""
705
+ logger.info(f"Attempting to duplicate '{source_repo_id}' to '{target_repo_id}' (private={private}).")
706
+ try:
707
+ token, err = _get_api_token(hf_api_key)
708
+ if err or not token:
709
+ logger.error(f"Token error duplicating space: {err or 'Token not found'}")
710
+ return f"Error getting token: {err or 'Token not found.'}"
711
+
712
+ # Validate target_repo_id format if it includes owner, extract owner/name
713
+ if '/' in target_repo_id:
714
+ target_owner, target_space_name = target_repo_id.split('/', 1)
715
+ if not target_owner or not target_space_name or '/' in target_space_name:
716
+ return f"Error: Invalid target repository ID format '{target_repo_id}'. Must be '<owner>/<space_name>'."
717
+ else:
718
+ # If only space name is provided, try to use the token's user as owner
719
+ target_space_name = target_repo_id
720
+ try:
721
+ user_info = whoami(token=token)
722
+ target_owner = user_info.get('name')
723
+ if not target_owner: raise Exception("Could not determine owner from token.")
724
+ target_repo_id = f"{target_owner}/{target_space_name}" # Update target_repo_id
725
+ except Exception as e:
726
+ logger.error(f"Could not determine target owner from token: {e}")
727
+ return f"Error: Target repository ID '{target_repo_id}' is missing owner, and owner could not be determined from token ({e}). Use '<owner>/<space_name>' format or set the Owner field."
728
+
729
+
730
+ hf_duplicate_repo(
731
+ from_repo=source_repo_id,
732
+ to_repo=target_repo_id,
733
+ repo_type="space",
734
+ token=token,
735
+ private=private,
736
+ # Use exist_ok=True? The UI should probably warn, but the backend can handle overwrite
737
+ # For now, let's assume overwrite is intended if triggered by AI/manual button after warning
738
+ exist_ok=True # Allow overwriting existing target space
739
+ )
740
+ logger.info(f"Successfully duplicated space from {source_repo_id} to {target_repo_id}.")
741
+ return f"Successfully duplicated space from `{source_repo_id}` to `{target_repo_id}`."
742
+ except HfHubHTTPError as e_http:
743
+ logger.error(f"HTTP error duplicating space from {source_repo_id} to {target_repo_id}: {e_http}")
744
+ status_code = e_http.response.status_code if e_http.response else 'N/A'
745
+ return f"HTTP Error ({status_code}) duplicating space: {e_http.response.text if e_http.response else str(e_http)}"
746
+ except Exception as e:
747
+ logger.exception(f"Error duplicating space from {source_repo_id} to {target_repo_id}:")
748
+ return f"Error duplicating space: {e}"
749
+
750
+ def list_user_spaces(hf_api_key, owner=None):
751
+ """Lists spaces for the authenticated user or a specific owner/org."""
752
+ logger.info(f"Attempting to list spaces for owner: {owner or 'authenticated user'}.")
753
+ try:
754
+ token, err = _get_api_token(hf_api_key)
755
+ if err or not token:
756
+ logger.error(f"Token error listing spaces: {err or 'Token not found'}")
757
+ return None, f"Error getting token: {err or 'Token not found.'}"
758
+
759
+ # If owner is not provided, list spaces for the authenticated user
760
+ # We need the username for list_repos filter if owner is None in UI
761
+ effective_owner = owner
762
+ if not effective_owner:
763
+ try:
764
+ user_info = whoami(token=token)
765
+ effective_owner = user_info.get('name')
766
+ if not effective_owner: raise Exception("Could not determine owner from token.")
767
+ logger.info(f"Listing spaces for auto-detected owner: {effective_owner}")
768
+ except Exception as e:
769
+ logger.error(f"Could not determine owner from token for listing: {e}")
770
+ # Continue trying list_repos without username filter? No, list_repos
771
+ # typically needs user or org specified for filtering unless it's public repos.
772
+ # Let's require owner or a valid token for user listing.
773
+ return None, f"Error auto-detecting owner for listing: {e}. Please specify Owner field."
774
+
775
+
776
+ api = HfApi(token=token)
777
+ spaces = hf_list_repos(author=effective_owner, type="space", token=token, timeout=20)
778
+ space_ids = [f"{r.author}/{r.id}" for r in spaces]
779
+
780
+ logger.info(f"Successfully listed {len(space_ids)} spaces for {effective_owner}.")
781
+ return space_ids, None
782
+
783
+ except HfHubHTTPError as e_http:
784
+ logger.error(f"HTTP error listing spaces for {owner or 'authenticated user'}: {e_http}")
785
+ status_code = e_http.response.status_code if e_http.response else 'N/A'
786
+ if status_code == 404:
787
+ # 404 could mean the owner doesn't exist or has no public spaces and token doesn't give access
788
+ return [], f"HTTP Error ({status_code}): Owner '{owner}' not found or has no accessible spaces."
789
+ if status_code in (401, 403):
790
+ return [], f"HTTP Error ({status_code}): Access denied or authentication required for listing spaces for '{owner}'. Check token permissions."
791
+ return None, f"HTTP Error ({status_code}) listing spaces for '{owner or 'authenticated user'}': {e_http.response.text if e_http.response else str(e_http)}"
792
+ except Exception as e:
793
+ logger.exception(f"Error listing spaces for {owner or 'authenticated user'}:")
794
+ return None, f"Error listing spaces: {e}"