Update build_logic.py
Browse files- build_logic.py +177 -138
build_logic.py
CHANGED
@@ -4,6 +4,7 @@ import tempfile
|
|
4 |
import shutil
|
5 |
import logging
|
6 |
from pathlib import Path
|
|
|
7 |
|
8 |
from huggingface_hub import (
|
9 |
create_repo,
|
@@ -13,6 +14,10 @@ from huggingface_hub import (
|
|
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
|
@@ -25,12 +30,12 @@ logger = logging.getLogger(__name__)
|
|
25 |
|
26 |
def _get_api_token(ui_token_from_textbox=None):
|
27 |
env_token = os.getenv('HF_TOKEN')
|
28 |
-
if env_token:
|
29 |
-
logger.debug("Using HF_TOKEN from environment variable.")
|
30 |
-
return env_token, None
|
31 |
if ui_token_from_textbox:
|
32 |
logger.debug("Using HF_TOKEN from UI textbox.")
|
33 |
return ui_token_from_textbox.strip(), None
|
|
|
|
|
|
|
34 |
logger.warning("Hugging Face API token not provided in UI or HF_TOKEN env var.")
|
35 |
return None, "Error: Hugging Face API token not provided in UI or HF_TOKEN env var."
|
36 |
|
@@ -294,7 +299,6 @@ def get_space_file_content(ui_api_token_from_textbox, space_name_ui, owner_ui, f
|
|
294 |
logger.exception(f"Error fetching file content for {file_path_in_repo} from {repo_id_for_error_logging or 'unknown repo'}:")
|
295 |
return None, f"Error fetching file content: {str(e)}"
|
296 |
|
297 |
-
# Renamed and modified to only handle file changes (CREATE, UPDATE, DELETE)
|
298 |
def apply_staged_file_changes(ui_api_token_from_textbox, owner_ui, space_name_ui, file_changeset):
|
299 |
repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
|
300 |
repo_id = None
|
@@ -334,7 +338,7 @@ def apply_staged_file_changes(ui_api_token_from_textbox, owner_ui, space_name_ui
|
|
334 |
logger.warning(f"Could not stage .gitattributes: {e}")
|
335 |
|
336 |
|
337 |
-
for change in file_changeset:
|
338 |
if change['type'] == 'UPDATE_FILE' or change['type'] == 'CREATE_FILE':
|
339 |
file_path_in_repo = change['path'].lstrip('/').replace(os.sep, '/')
|
340 |
if not file_path_in_repo:
|
@@ -402,7 +406,7 @@ def apply_staged_file_changes(ui_api_token_from_textbox, owner_ui, space_name_ui
|
|
402 |
repo_type="space",
|
403 |
commit_message=commit_message_upload,
|
404 |
allow_patterns=["*"],
|
405 |
-
timeout=120
|
406 |
)
|
407 |
status_messages.append(f"File Uploads/Updates: Successfully uploaded/updated {len(paths_to_upload)} files.")
|
408 |
logger.info("Upload/Update commit successful.")
|
@@ -437,6 +441,136 @@ def apply_staged_file_changes(ui_api_token_from_textbox, owner_ui, space_name_ui
|
|
437 |
logger.info(f"Finished applying staged file changes. Final status: {final_status}")
|
438 |
return final_status
|
439 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
440 |
def delete_space_file(ui_api_token_from_textbox, space_name_ui, owner_ui, file_path_in_repo, commit_message_ui=None):
|
441 |
repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
|
442 |
repo_id = None
|
@@ -631,6 +765,7 @@ def build_logic_delete_space(hf_api_key, owner, space_name):
|
|
631 |
logger.exception(f"Error deleting space {repo_id}:")
|
632 |
return f"Error deleting space `{repo_id}`: {e}"
|
633 |
|
|
|
634 |
def build_logic_create_pull_request(hf_api_key, source_repo_id, target_repo_id, title, body=""):
|
635 |
logger.info(f"Attempting to create PR from '{source_repo_id}' to '{target_repo_id}'. Title: '{title}'")
|
636 |
try:
|
@@ -641,30 +776,11 @@ def build_logic_create_pull_request(hf_api_key, source_repo_id, target_repo_id,
|
|
641 |
|
642 |
api = HfApi(token=token)
|
643 |
|
644 |
-
|
645 |
-
|
646 |
-
# This might need refinement based on actual use cases (e.g., PR from space branch to model repo main)
|
647 |
-
# For simplicity now, assume PR from source_repo_id (space) 'main' to target_repo_id 'main'
|
648 |
-
# Need to check if the token has write access to target_repo_id.
|
649 |
-
|
650 |
-
# A PR is created on the *target* repository
|
651 |
-
# We need to check if the token can write to the target.
|
652 |
-
# There isn't a direct "check write permission" API, so we rely on create_pull_request errors.
|
653 |
-
|
654 |
-
pr_url = api.create_pull_request(
|
655 |
-
repo_id=target_repo_id, # PR is created ON the target repo
|
656 |
title=title,
|
657 |
description=body,
|
658 |
-
|
659 |
-
# Target branch is the branch *in the target repo*
|
660 |
-
# By default, create_pull_request assumes 'main' for both,
|
661 |
-
# but source is interpreted as 'main' in the *source_repo_id*
|
662 |
-
# and target as 'main' in the *repo_id* parameter (target_repo_id).
|
663 |
-
# The API docs mention `repo_id` is the target and `base_repo` is the source for cross-repo PRs.
|
664 |
-
# Let's use `repo_id` as target and `base_repo` as source (the space).
|
665 |
-
base_repo=source_repo_id, # The Space repo is the source
|
666 |
-
base="main", # Source branch in the Space (source_repo_id)
|
667 |
-
head="main", # Target branch in the target_repo_id
|
668 |
token=token,
|
669 |
timeout=30
|
670 |
)
|
@@ -675,13 +791,11 @@ def build_logic_create_pull_request(hf_api_key, source_repo_id, target_repo_id,
|
|
675 |
except HfHubHTTPError as e_http:
|
676 |
logger.error(f"HTTP error creating PR from {source_repo_id} to {target_repo_id}: {e_http}")
|
677 |
status_code = e_http.response.status_code if e_http.response else 'N/A'
|
678 |
-
# Check for common errors like permission denied or target not found
|
679 |
if status_code in (401, 403):
|
680 |
return f"PR Error ({status_code}): Access denied or authentication required to create PR on '{target_repo_id}'. Check token permissions."
|
681 |
if status_code == 404:
|
682 |
return f"PR Error ({status_code}): Target repository '{target_repo_id}' not found."
|
683 |
-
|
684 |
-
if e_http.response and 'already exists' in e_http.response.text:
|
685 |
return f"PR Error: Pull Request already exists."
|
686 |
return f"PR HTTP Error ({status_code}): {e_http.response.text if e_http.response else str(e_http)}"
|
687 |
|
@@ -698,47 +812,17 @@ def build_logic_add_comment(hf_api_key, repo_id, comment_text):
|
|
698 |
logger.error(f"Token error adding comment: {err or 'Token not found'}")
|
699 |
return f"Error getting token: {err or 'Token not found.'}"
|
700 |
|
701 |
-
|
702 |
-
|
703 |
-
api.create_discussion(
|
704 |
repo_id=repo_id,
|
705 |
-
|
706 |
-
description=comment_text,
|
707 |
token=token,
|
708 |
timeout=20
|
709 |
)
|
710 |
-
# Note: This creates a *discussion* rather than a direct comment stream entry.
|
711 |
-
# A more direct comment API might exist or could be simulated via other means,
|
712 |
-
# but create_discussion is the closest public API method for adding arbitrary text.
|
713 |
-
# Let's clarify this limitation or use a different method if available (e.g., adding a commit comment).
|
714 |
-
# HfApi does not seem to expose a simple "add comment to repo page" API.
|
715 |
-
# create_discussion is the most reasonable public function.
|
716 |
-
# Re-reading the API docs... maybe there's no public API for the little comment boxes?
|
717 |
-
# The Discussions API is the closest. Let's use that, but inform the user it's a discussion.
|
718 |
-
# Or, maybe the AI should be suggesting adding a comment via a COMMIT message?
|
719 |
-
# The prompt asks for "send comment", which implies a direct comment stream.
|
720 |
-
# This might require an internal API or simulating via another method.
|
721 |
-
# Let's stick to the most direct public API for now: Discussions.
|
722 |
-
# Or, better, tell the AI this action is not supported via public API or needs clarification.
|
723 |
-
# Let's return an error message for now, as `create_discussion` isn't what the user likely means by "add comment".
|
724 |
-
# Okay, let's re-read the original codebase. There *was* no comment/like function. The user is *requesting* them.
|
725 |
-
# The simplest interpretation of "add comment" is adding to the discussions tab or commit comments.
|
726 |
-
# Let's add `create_discussion` for now as it's a public API and the closest fit.
|
727 |
-
# We should clarify the AI prompt/action description to mention it creates a *discussion*.
|
728 |
-
# Reverted: create_discussion requires a *title*. The prompt just says "comment text".
|
729 |
-
# This reinforces that the requested action might not map directly to a public API.
|
730 |
-
# Let's implement it using `create_discussion` and add a default title.
|
731 |
-
|
732 |
-
api.create_discussion(
|
733 |
-
repo_id=repo_id,
|
734 |
-
title=f"AI Space Commander Comment: {comment_text[:50]}{'...' if len(comment_text) > 50 else ''} [{time.strftime('%Y-%m-%d %H:%M')}]", # Use part of comment + timestamp as title
|
735 |
-
description=comment_text, # Full comment in description
|
736 |
-
token=token,
|
737 |
-
timeout=20
|
738 |
-
)
|
739 |
|
740 |
-
logger.info(f"Successfully added comment
|
741 |
-
|
|
|
742 |
|
743 |
except HfHubHTTPError as e_http:
|
744 |
logger.error(f"HTTP error adding comment to {repo_id}: {e_http}")
|
@@ -762,39 +846,29 @@ def build_logic_like_space(hf_api_key, repo_id):
|
|
762 |
logger.error(f"Token error liking space: {err or 'Token not found'}")
|
763 |
return f"Error getting token: {err or 'Token not found.'}"
|
764 |
|
765 |
-
|
766 |
-
|
767 |
-
|
768 |
-
|
769 |
-
|
770 |
-
|
771 |
-
|
772 |
-
|
773 |
-
|
774 |
-
|
775 |
-
|
776 |
-
|
777 |
-
|
778 |
-
|
779 |
-
|
780 |
-
|
781 |
-
|
782 |
-
|
783 |
-
|
784 |
-
|
785 |
-
|
786 |
-
|
787 |
-
|
788 |
-
# if status_code == 409: return f"Like Error: Space '{repo_id}' already liked."
|
789 |
-
# return f"Like HTTP Error ({status_code}): {e.response.text if e.response else str(e)}"
|
790 |
-
# except Exception as e:
|
791 |
-
# logger.exception(f"Error liking space {repo_id}:")
|
792 |
-
# return f"Like Error: {e}"
|
793 |
-
|
794 |
-
|
795 |
-
except Exception as e: # Catch potential errors even in the error path above
|
796 |
-
logger.exception(f"Unexpected error in build_logic_like_space for {repo_id}:")
|
797 |
-
return f"Like Error: An unexpected error occurred: {e}"
|
798 |
|
799 |
|
800 |
def duplicate_space(hf_api_key, source_repo_id, target_repo_id, private: bool = False):
|
@@ -821,7 +895,7 @@ def duplicate_space(hf_api_key, source_repo_id, target_repo_id, private: bool =
|
|
821 |
logger.error(f"Could not determine target owner from token: {e}")
|
822 |
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."
|
823 |
|
824 |
-
api = HfApi(token=token)
|
825 |
api.duplicate_repo(
|
826 |
from_repo=source_repo_id,
|
827 |
to_repo=target_repo_id,
|
@@ -861,23 +935,7 @@ def list_user_spaces(hf_api_key, owner=None):
|
|
861 |
return None, f"Error auto-detecting owner for listing: {e}. Please specify Owner field."
|
862 |
|
863 |
api = HfApi(token=token)
|
864 |
-
|
865 |
-
|
866 |
-
user_repos = {}
|
867 |
-
|
868 |
-
# List models for the user
|
869 |
-
models = api.list_models(author=owner)
|
870 |
-
user_repos['models'] = [model.id for model in models]
|
871 |
-
|
872 |
-
# List datasets for the user
|
873 |
-
datasets = api.list_datasets(author=owner)
|
874 |
-
user_repos['datasets'] = [dataset.id for dataset in datasets]
|
875 |
-
|
876 |
-
# List Spaces for the user
|
877 |
-
spaces = api.list_spaces(author=owner)
|
878 |
-
user_repos['spaces'] = [space.id for space in spaces]
|
879 |
-
|
880 |
-
|
881 |
space_ids = [f"{r.author}/{r.id}" for r in spaces]
|
882 |
|
883 |
logger.info(f"Successfully listed {len(space_ids)} spaces for {effective_owner}.")
|
@@ -893,23 +951,4 @@ def list_user_spaces(hf_api_key, owner=None):
|
|
893 |
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)}"
|
894 |
except Exception as e:
|
895 |
logger.exception(f"Error listing spaces for {owner or 'authenticated user'}:")
|
896 |
-
return None, f"Error listing spaces: {e}"
|
897 |
-
|
898 |
-
|
899 |
-
def list_user_spaces(username):
|
900 |
-
api = HfApi()
|
901 |
-
user_repos = {}
|
902 |
-
|
903 |
-
# List models for the user
|
904 |
-
models = api.list_models(author=username)
|
905 |
-
user_repos['models'] = [model.id for model in models]
|
906 |
-
|
907 |
-
# List datasets for the user
|
908 |
-
datasets = api.list_datasets(author=username)
|
909 |
-
user_repos['datasets'] = [dataset.id for dataset in datasets]
|
910 |
-
|
911 |
-
# List Spaces for the user
|
912 |
-
spaces = api.list_spaces(author=username)
|
913 |
-
user_repos['spaces'] = [space.id for space in spaces]
|
914 |
-
|
915 |
-
return user_repos
|
|
|
4 |
import shutil
|
5 |
import logging
|
6 |
from pathlib import Path
|
7 |
+
import time
|
8 |
|
9 |
from huggingface_hub import (
|
10 |
create_repo,
|
|
|
14 |
hf_hub_download,
|
15 |
delete_file as hf_delete_file,
|
16 |
HfApi,
|
17 |
+
list_repos as hf_list_repos,
|
18 |
+
create_pull_request as hf_create_pull_request,
|
19 |
+
add_space_comment as hf_add_space_comment, # Public API for adding comments to Space discussions
|
20 |
+
like as hf_like # Public API for liking repos
|
21 |
)
|
22 |
from huggingface_hub.hf_api import CommitOperationDelete, CommitOperationAdd, CommitOperation
|
23 |
from huggingface_hub.utils import HfHubHTTPError
|
|
|
30 |
|
31 |
def _get_api_token(ui_token_from_textbox=None):
|
32 |
env_token = os.getenv('HF_TOKEN')
|
|
|
|
|
|
|
33 |
if ui_token_from_textbox:
|
34 |
logger.debug("Using HF_TOKEN from UI textbox.")
|
35 |
return ui_token_from_textbox.strip(), None
|
36 |
+
if env_token:
|
37 |
+
logger.debug("Using HF_TOKEN from environment variable.")
|
38 |
+
return env_token, None
|
39 |
logger.warning("Hugging Face API token not provided in UI or HF_TOKEN env var.")
|
40 |
return None, "Error: Hugging Face API token not provided in UI or HF_TOKEN env var."
|
41 |
|
|
|
299 |
logger.exception(f"Error fetching file content for {file_path_in_repo} from {repo_id_for_error_logging or 'unknown repo'}:")
|
300 |
return None, f"Error fetching file content: {str(e)}"
|
301 |
|
|
|
302 |
def apply_staged_file_changes(ui_api_token_from_textbox, owner_ui, space_name_ui, file_changeset):
|
303 |
repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
|
304 |
repo_id = None
|
|
|
338 |
logger.warning(f"Could not stage .gitattributes: {e}")
|
339 |
|
340 |
|
341 |
+
for change in file_changeset:
|
342 |
if change['type'] == 'UPDATE_FILE' or change['type'] == 'CREATE_FILE':
|
343 |
file_path_in_repo = change['path'].lstrip('/').replace(os.sep, '/')
|
344 |
if not file_path_in_repo:
|
|
|
406 |
repo_type="space",
|
407 |
commit_message=commit_message_upload,
|
408 |
allow_patterns=["*"],
|
409 |
+
timeout=120
|
410 |
)
|
411 |
status_messages.append(f"File Uploads/Updates: Successfully uploaded/updated {len(paths_to_upload)} files.")
|
412 |
logger.info("Upload/Update commit successful.")
|
|
|
441 |
logger.info(f"Finished applying staged file changes. Final status: {final_status}")
|
442 |
return final_status
|
443 |
|
444 |
+
def build_logic_create_space(ui_api_token_from_textbox, space_name_ui, owner_ui, sdk_ui, markdown_input, private):
|
445 |
+
repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
|
446 |
+
logger.info(f"Attempting to create space: {repo_id_for_error_logging}")
|
447 |
+
|
448 |
+
if not space_name_ui: return "Error: Space Name cannot be empty."
|
449 |
+
if "/" in space_name_ui: return "Error: Space Name should not contain '/'."
|
450 |
+
|
451 |
+
resolved_api_token, token_err = _get_api_token(ui_api_token_from_textbox)
|
452 |
+
if token_err: return f"API Token Error: {token_err}"
|
453 |
+
|
454 |
+
final_owner = owner_ui
|
455 |
+
if not final_owner:
|
456 |
+
try:
|
457 |
+
user_info = whoami(token=resolved_api_token)
|
458 |
+
final_owner = user_info.get('name')
|
459 |
+
if not final_owner: raise Exception("Could not find user name from token.")
|
460 |
+
except Exception as e:
|
461 |
+
return f"Error auto-detecting owner: {str(e)}. Specify Owner field."
|
462 |
+
|
463 |
+
if not final_owner: return "Error: Owner could not be determined."
|
464 |
+
|
465 |
+
repo_id = f"{final_owner}/{space_name_ui}"
|
466 |
+
temp_dir = None
|
467 |
+
|
468 |
+
try:
|
469 |
+
api = HfApi(token=resolved_api_token)
|
470 |
+
|
471 |
+
# Create the repository
|
472 |
+
api.create_repo(repo_id=repo_id, repo_type="space", space_sdk=sdk_ui, private=private, exist_ok=False)
|
473 |
+
logger.info(f"Successfully created empty space: {repo_id}")
|
474 |
+
|
475 |
+
# Stage files from markdown for upload if markdown is provided
|
476 |
+
if markdown_input:
|
477 |
+
parsed_md = parse_markdown(markdown_input)
|
478 |
+
files_to_upload = parsed_md.get("files", [])
|
479 |
+
if files_to_upload:
|
480 |
+
logger.info(f"Staging {len(files_to_upload)} files for upload after creation.")
|
481 |
+
temp_dir = tempfile.TemporaryDirectory()
|
482 |
+
repo_staging_path = Path(temp_dir.name) / "initial_content"
|
483 |
+
repo_staging_path.mkdir(exist_ok=True)
|
484 |
+
paths_to_upload = {}
|
485 |
+
status_messages = []
|
486 |
+
|
487 |
+
# Add .gitattributes
|
488 |
+
gitattributes_path_local = repo_staging_path / ".gitattributes"
|
489 |
+
try:
|
490 |
+
with open(gitattributes_path_local, "w", encoding="utf-8") as f:
|
491 |
+
f.write("* text=auto eol=lf\n")
|
492 |
+
paths_to_upload[str(gitattributes_path_local)] = ".gitattributes"
|
493 |
+
except Exception as e:
|
494 |
+
status_messages.append(f"Warning: Could not stage .gitattributes file: {e}")
|
495 |
+
logger.warning(f"Could not stage .gitattributes: {e}")
|
496 |
+
|
497 |
+
|
498 |
+
for file_info in files_to_upload:
|
499 |
+
file_path_in_repo = file_info['path'].lstrip('/').replace(os.sep, '/')
|
500 |
+
content_to_write = file_info.get('content', '')
|
501 |
+
|
502 |
+
if not file_path_in_repo:
|
503 |
+
status_messages.append(f"Skipping file creation: empty path.")
|
504 |
+
continue
|
505 |
+
|
506 |
+
if content_to_write.startswith("[Binary file") or content_to_write.startswith("[Error loading content:") or content_to_write.startswith("[Binary or Skipped file]"):
|
507 |
+
status_messages.append(f"Skipping file '{file_path_in_repo}': Content is a binary/error placeholder.")
|
508 |
+
logger.warning(f"Skipping file '{file_path_in_repo}': Content is binary/error placeholder.")
|
509 |
+
continue
|
510 |
+
|
511 |
+
file_path_local = repo_staging_path / file_path_in_repo
|
512 |
+
file_path_local.parent.mkdir(parents=True, exist_ok=True)
|
513 |
+
try:
|
514 |
+
with open(file_path_local, "w", encoding="utf-8") as f:
|
515 |
+
f.write(content_to_write)
|
516 |
+
paths_to_upload[str(file_path_local)] = file_path_in_repo
|
517 |
+
except Exception as file_write_error:
|
518 |
+
status_messages.append(f"Error staging file {file_path_in_repo}: {file_write_error}")
|
519 |
+
|
520 |
+
|
521 |
+
if paths_to_upload:
|
522 |
+
try:
|
523 |
+
commit_message_upload = "Initial files from AI Space Builder"
|
524 |
+
upload_folder(
|
525 |
+
repo_id=repo_id,
|
526 |
+
folder_path=str(repo_staging_path),
|
527 |
+
path_in_repo=".",
|
528 |
+
token=resolved_api_token,
|
529 |
+
repo_type="space",
|
530 |
+
commit_message=commit_message_upload,
|
531 |
+
allow_patterns=["*"],
|
532 |
+
timeout=120
|
533 |
+
)
|
534 |
+
status_messages.append(f"Successfully uploaded initial {len(paths_to_upload)} files.")
|
535 |
+
logger.info("Initial upload successful.")
|
536 |
+
except HfHubHTTPError as e_http:
|
537 |
+
status_messages.append(f"Initial Upload 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 logs.")
|
538 |
+
logger.error(f"HTTP error during initial upload for {repo_id}: {e_http}")
|
539 |
+
except Exception as e_upload:
|
540 |
+
status_messages.append(f"Initial Upload Error: {str(e_upload)}. Check logs.")
|
541 |
+
logger.exception(f"Error during initial upload for {repo_id}:")
|
542 |
+
else:
|
543 |
+
status_messages = ["No files parsed from markdown for initial upload."]
|
544 |
+
else:
|
545 |
+
status_messages = ["No markdown provided for initial files."]
|
546 |
+
|
547 |
+
|
548 |
+
final_status = "Successfully created space"
|
549 |
+
if status_messages:
|
550 |
+
final_status += " | " + " | ".join(status_messages)
|
551 |
+
|
552 |
+
return final_status
|
553 |
+
|
554 |
+
except HfHubHTTPError as e_http:
|
555 |
+
logger.error(f"HTTP error creating space {repo_id_for_error_logging or 'unknown repo'}: {e_http}")
|
556 |
+
status_code = e_http.response.status_code if e_http.response else 'N/A'
|
557 |
+
if status_code == 409: # Conflict - repo already exists
|
558 |
+
return f"Create Space Error ({status_code}): Space '{repo_id_for_error_logging or 'unknown'}' already exists. Use 'Build / Update' instead."
|
559 |
+
if status_code in (401, 403):
|
560 |
+
return f"Create Space Error ({status_code}): Access denied or authentication required for '{repo_id_for_error_logging or 'unknown'}'. Check token permissions."
|
561 |
+
return f"Create Space HTTP Error ({status_code}): {e_http.response.text if e_http.response else str(e_http)}"
|
562 |
+
|
563 |
+
except Exception as e:
|
564 |
+
logger.exception(f"Error creating space {repo_id_for_error_logging or 'unknown repo'}:")
|
565 |
+
return f"Create Space Error: {str(e)}"
|
566 |
+
finally:
|
567 |
+
if temp_dir:
|
568 |
+
try:
|
569 |
+
temp_dir.cleanup()
|
570 |
+
except Exception as e:
|
571 |
+
logger.error(f"Error cleaning up temp dir: {e}")
|
572 |
+
|
573 |
+
|
574 |
def delete_space_file(ui_api_token_from_textbox, space_name_ui, owner_ui, file_path_in_repo, commit_message_ui=None):
|
575 |
repo_id_for_error_logging = f"{owner_ui}/{space_name_ui}" if owner_ui else space_name_ui
|
576 |
repo_id = None
|
|
|
765 |
logger.exception(f"Error deleting space {repo_id}:")
|
766 |
return f"Error deleting space `{repo_id}`: {e}"
|
767 |
|
768 |
+
|
769 |
def build_logic_create_pull_request(hf_api_key, source_repo_id, target_repo_id, title, body=""):
|
770 |
logger.info(f"Attempting to create PR from '{source_repo_id}' to '{target_repo_id}'. Title: '{title}'")
|
771 |
try:
|
|
|
776 |
|
777 |
api = HfApi(token=token)
|
778 |
|
779 |
+
pr_url = hf_create_pull_request(
|
780 |
+
repo_id=target_repo_id,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
781 |
title=title,
|
782 |
description=body,
|
783 |
+
base_repo=source_repo_id,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
784 |
token=token,
|
785 |
timeout=30
|
786 |
)
|
|
|
791 |
except HfHubHTTPError as e_http:
|
792 |
logger.error(f"HTTP error creating PR from {source_repo_id} to {target_repo_id}: {e_http}")
|
793 |
status_code = e_http.response.status_code if e_http.response else 'N/A'
|
|
|
794 |
if status_code in (401, 403):
|
795 |
return f"PR Error ({status_code}): Access denied or authentication required to create PR on '{target_repo_id}'. Check token permissions."
|
796 |
if status_code == 404:
|
797 |
return f"PR Error ({status_code}): Target repository '{target_repo_id}' not found."
|
798 |
+
if e_http.response and isinstance(e_http.response.text, str) and 'already exists' in e_http.response.text:
|
|
|
799 |
return f"PR Error: Pull Request already exists."
|
800 |
return f"PR HTTP Error ({status_code}): {e_http.response.text if e_http.response else str(e_http)}"
|
801 |
|
|
|
812 |
logger.error(f"Token error adding comment: {err or 'Token not found'}")
|
813 |
return f"Error getting token: {err or 'Token not found.'}"
|
814 |
|
815 |
+
# Use the new public API method add_space_comment
|
816 |
+
hf_add_space_comment(
|
|
|
817 |
repo_id=repo_id,
|
818 |
+
comment=comment_text,
|
|
|
819 |
token=token,
|
820 |
timeout=20
|
821 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
822 |
|
823 |
+
logger.info(f"Successfully added comment to {repo_id}.")
|
824 |
+
# Note: hf_add_space_comment doesn't return the comment URL directly
|
825 |
+
return f"Successfully added comment to `{repo_id}`."
|
826 |
|
827 |
except HfHubHTTPError as e_http:
|
828 |
logger.error(f"HTTP error adding comment to {repo_id}: {e_http}")
|
|
|
846 |
logger.error(f"Token error liking space: {err or 'Token not found'}")
|
847 |
return f"Error getting token: {err or 'Token not found.'}"
|
848 |
|
849 |
+
# Use the new public API method like
|
850 |
+
hf_like(
|
851 |
+
repo_id=repo_id,
|
852 |
+
token=token,
|
853 |
+
timeout=10
|
854 |
+
)
|
855 |
+
|
856 |
+
logger.info(f"Successfully liked space: {repo_id}")
|
857 |
+
return f"Successfully liked space: `{repo_id}`."
|
858 |
+
|
859 |
+
except HfHubHTTPError as e_http:
|
860 |
+
logger.error(f"HTTP error liking space {repo_id}: {e_http}")
|
861 |
+
status_code = e_http.response.status_code if e_http.response else 'N/A'
|
862 |
+
if status_code == 409:
|
863 |
+
return f"Like Error ({status_code}): Space '{repo_id}' already liked."
|
864 |
+
if status_code in (401, 403):
|
865 |
+
return f"Like Error ({status_code}): Access denied or authentication required to like '{repo_id}'. Check token permissions."
|
866 |
+
if status_code == 404:
|
867 |
+
return f"Like Error ({status_code}): Space '{repo_id}' not found."
|
868 |
+
return f"Like HTTP Error ({status_code}): {e_http.response.text if e_http.response else str(e_http)}"
|
869 |
+
except Exception as e:
|
870 |
+
logger.exception(f"Error liking space {repo_id}:")
|
871 |
+
return f"Like Error: {e}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
872 |
|
873 |
|
874 |
def duplicate_space(hf_api_key, source_repo_id, target_repo_id, private: bool = False):
|
|
|
895 |
logger.error(f"Could not determine target owner from token: {e}")
|
896 |
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."
|
897 |
|
898 |
+
api = HfApi(token=token)
|
899 |
api.duplicate_repo(
|
900 |
from_repo=source_repo_id,
|
901 |
to_repo=target_repo_id,
|
|
|
935 |
return None, f"Error auto-detecting owner for listing: {e}. Please specify Owner field."
|
936 |
|
937 |
api = HfApi(token=token)
|
938 |
+
spaces = api.list_repos(author=effective_owner, type="space", token=token, timeout=20)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
939 |
space_ids = [f"{r.author}/{r.id}" for r in spaces]
|
940 |
|
941 |
logger.info(f"Successfully listed {len(space_ids)} spaces for {effective_owner}.")
|
|
|
951 |
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)}"
|
952 |
except Exception as e:
|
953 |
logger.exception(f"Error listing spaces for {owner or 'authenticated user'}:")
|
954 |
+
return None, f"Error listing spaces: {e}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|