Update build_logic.py
Browse files- 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 |
-
|
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}"
|