Spaces:
Build error
Build error
| # huggingface_storage.py | |
| import os | |
| import shutil | |
| import logging | |
| from huggingface_hub import HfApi, HfFileSystem | |
| from huggingface_hub.utils import HfHubHTTPError # <-- LIGNE CORRIGÉE | |
| from typing import Optional, Dict | |
| logger = logging.getLogger(__name__) | |
| # --- Configuration Hugging Face --- | |
| # Le token sera automatiquement chargé par HfApi si la variable d'environnement HF_TOKEN est définie. | |
| # Repo ID du dataset (Ex: 'mon_utilisateur/mon_dataset_sites_web') | |
| HF_DATASET_REPO_ID = os.environ.get("HF_DATASET_REPO_ID") | |
| api = HfApi() | |
| fs = HfFileSystem() | |
| def upload_project_folder_to_hf( | |
| local_folder_path: str, | |
| deploy_id: str, | |
| repo_id: Optional[str] = None | |
| ) -> tuple[bool, str, Optional[str]]: | |
| """ | |
| Téléverse un dossier local (le projet utilisateur) vers un dossier spécifique | |
| dans le Dataset Hugging Face, en une seule commit, en utilisant deploy_id comme path_in_repo. | |
| :param local_folder_path: Chemin absolu du dossier local à téléverser (ex: temp_uploads/abc-123). | |
| :param deploy_id: L'ID unique du projet (utilisé comme nom de dossier dans le repo HF). | |
| :param repo_id: L'ID complet du repo HF (si non fourni, utilise HF_DATASET_REPO_ID). | |
| :return: (Succès: bool, Message: str, Chemin complet du repo: Optional[str]) | |
| """ | |
| repo_id = repo_id if repo_id else HF_DATASET_REPO_ID | |
| if not repo_id: | |
| return False, "Erreur de configuration: Repository Hugging Face non défini (HF_DATASET_REPO_ID manquant).", None | |
| try: | |
| if not os.path.isdir(local_folder_path): | |
| return False, f"Erreur: Le dossier local pour le déploiement ID '{deploy_id}' est introuvable.", None | |
| # path_in_repo est le chemin où les fichiers seront stockés dans le repo (le dossier du projet) | |
| hf_folder_path = f"{deploy_id}" | |
| logger.info(f"Début du téléversement de {local_folder_path} vers HF repo: {repo_id}/{hf_folder_path}") | |
| api.upload_folder( | |
| folder_path=local_folder_path, | |
| repo_id=repo_id, | |
| path_in_repo=hf_folder_path, | |
| repo_type="dataset", | |
| commit_message=f"Ajout du site web statique pour le projet ID: {deploy_id}", | |
| delete_old_files=True | |
| ) | |
| full_repo_path = f"https://huggingface.co/datasets/{repo_id}/tree/main/{deploy_id}" | |
| logger.info(f"Téléversement HF réussi pour le projet {deploy_id}.") | |
| return True, "Téléversement vers Hugging Face réussi.", full_repo_path | |
| except HfHubHTTPError as e: | |
| error_message = f"Erreur API Hugging Face (vérifiez votre jeton d'API et les permissions): {e.response.text}" | |
| logger.error(error_message) | |
| return False, error_message, None | |
| except Exception as e: | |
| logger.error(f"Erreur lors du téléchargement HF pour {deploy_id}: {e}") | |
| return False, f"Échec du téléchargement Hugging Face: {str(e)}", None | |
| def get_project_files_from_hf( | |
| deploy_id: str, | |
| local_target_path: str, | |
| repo_id: Optional[str] = None | |
| ) -> tuple[bool, str]: | |
| """ | |
| Télécharge le dossier d'un projet depuis Hugging Face vers un dossier local. | |
| """ | |
| repo_id = repo_id if repo_id else HF_DATASET_REPO_ID | |
| if not repo_id: | |
| return False, "Erreur de configuration: Repository Hugging Face non défini." | |
| try: | |
| # Créer le dossier cible local | |
| if os.path.exists(local_target_path): | |
| shutil.rmtree(local_target_path) | |
| os.makedirs(local_target_path, exist_ok=True) | |
| # Chemin complet vers le dossier du projet dans le dépôt Hugging Face | |
| hf_folder_path = f"datasets/{repo_id}/{deploy_id}" | |
| if not fs.isdir(hf_folder_path): | |
| return False, f"Projet non trouvé sur Hugging Face: {deploy_id}" | |
| # Télécharger récursivement le dossier du projet | |
| # fs.get(chemin_distant, chemin_local) | |
| fs.get(hf_folder_path, local_target_path, recursive=True) | |
| logger.info(f"Téléchargement réussi du projet {deploy_id} vers {local_target_path}.") | |
| return True, "Dossier de projet téléchargé avec succès." | |
| except Exception as e: | |
| logger.error(f"Erreur lors du téléchargement HF pour {deploy_id}: {e}") | |
| return False, f"Échec du téléchargement Hugging Face: {str(e)}" | |
| def delete_project_folder_from_hf( | |
| deploy_id: str, | |
| repo_id: Optional[str] = None | |
| ) -> tuple[bool, str]: | |
| """ | |
| Supprime un dossier (projet utilisateur) complet du Dataset Hugging Face. | |
| """ | |
| repo_id = repo_id if repo_id else HF_DATASET_REPO_ID | |
| if not repo_id: | |
| return False, "Erreur de configuration: Repository Hugging Face non défini." | |
| try: | |
| # delete_folder crée une seule commit pour la suppression du dossier. | |
| commit_url = api.delete_folder( | |
| path_in_repo=deploy_id, | |
| repo_id=repo_id, | |
| repo_type="dataset", | |
| commit_message=f"Suppression du projet utilisateur ID: {deploy_id}" | |
| ) | |
| logger.info(f"Suppression réussie du projet {deploy_id}. URL de commit: {commit_url}") | |
| return True, "Dossier de projet supprimé avec succès de Hugging Face." | |
| except Exception as e: | |
| logger.error(f"Erreur inconnue lors de la suppression HF pour {deploy_id}: {e}") | |
| # Si le dossier est déjà supprimé, HfHubHTTPError 404 est levée par delete_folder | |
| if "404" in str(e): | |
| return True, "Dossier non trouvé sur Hugging Face (déjà supprimé ou ID invalide)." | |
| return False, f"Échec de la suppression Hugging Face: {str(e)}" | |
| def create_empty_repo_folder_on_hf( | |
| repo_slug: str, | |
| user_id: str, | |
| repo_id: Optional[str] = None | |
| ) -> tuple[bool, str]: | |
| """ | |
| Crée une structure de dossier vide (représentant le dépôt) sur le Dataset Hugging Face. | |
| Ceci est souvent implémenté en créant un fichier .gitkeep ou README.md initial. | |
| Nous allons créer un fichier README.md minimal. | |
| """ | |
| repo_id = repo_id if repo_id else HF_DATASET_REPO_ID | |
| if not repo_id: | |
| return False, "Erreur de configuration: Repository Hugging Face non défini." | |
| readme_content = f"# Dépôt {repo_slug}\n\nBienvenue sur votre dépôt GitForge, hébergé sur Mailix.\n\nCommencez par ajouter ou créer des fichiers." | |
| path_in_repo = f"{user_id}/{repo_slug}/README.md" | |
| try: | |
| # Utilisation de HfApi.upload_file pour créer un fichier unique, ce qui crée le dossier | |
| commit_url = api.upload_file( | |
| path_or_fileobj=readme_content.encode('utf-8'), | |
| path_in_repo=path_in_repo, | |
| repo_id=repo_id, | |
| repo_type="dataset", | |
| commit_message=f"Initialisation du dépôt {repo_slug}" | |
| ) | |
| logger.info(f"Dépôt vide créé sur HF pour {repo_slug}. Commit: {commit_url}") | |
| return True, "Dépôt initialisé avec succès sur Hugging Face." | |
| except Exception as e: | |
| logger.error(f"Échec de l'initialisation du dépôt HF pour {repo_slug}: {e}") | |
| return False, f"Échec de l'initialisation Hugging Face: {str(e)}" | |
| def list_repo_files_from_hf( | |
| repo_slug: str, | |
| user_id: str, | |
| repo_id: Optional[str] = None | |
| ) -> tuple[list[Dict], str]: | |
| """ | |
| Liste tous les fichiers et dossiers dans le chemin spécifique (user_id/repo_slug) | |
| du Dataset Hugging Face. | |
| """ | |
| repo_id = repo_id if repo_id else HF_DATASET_REPO_ID | |
| if not repo_id: | |
| return [], "Erreur de configuration: Repository Hugging Face non défini." | |
| # Le chemin dans le repo est 'user_id/repo_slug' | |
| path_to_repo = f"{user_id}/{repo_slug}" | |
| try: | |
| # Utiliser HfFileSystem pour lister le contenu du dossier | |
| items = fs.ls(f"datasets/{repo_id}/{path_to_repo}", detail=True) | |
| file_list = [] | |
| for item in items: | |
| # item est un dictionnaire avec 'name', 'type', 'size', 'last_modified', etc. | |
| # On ignore le nom du dossier racine pour l'affichage utilisateur | |
| file_name = item['name'].split('/')[-1] | |
| # Ignorer les dossiers et les fichiers système cachés (.DS_Store, .gitattributes, etc.) | |
| if item['type'] == 'directory' or file_name.startswith('.'): | |
| # Pour une future étape, on pourrait inclure les dossiers | |
| continue | |
| file_list.append({ | |
| "name": file_name, | |
| "path": item['name'], # Chemin complet pour référence interne | |
| "size": item.get('size', 0), | |
| "type": "file", # Dans cette version, nous listons seulement les fichiers | |
| "last_modified": item.get('last_modified', 'N/A') | |
| }) | |
| logger.info(f"Liste des fichiers récupérée pour {path_to_repo} ({len(file_list)} fichiers).") | |
| return file_list, "Liste des fichiers récupérée avec succès." | |
| except HfHubHTTPError as e: | |
| if "404" in str(e): | |
| # Le dossier n'existe pas encore (dépôt vide sans initialisation) | |
| return [], f"Le dépôt '{repo_slug}' n'a pas encore de fichiers sur Hugging Face (404)." | |
| logger.error(f"Erreur HTTP HF lors de la liste des fichiers pour {path_to_repo}: {e}") | |
| return [], f"Erreur Hugging Face: {str(e)}" | |
| except Exception as e: | |
| logger.error(f"Erreur inconnue lors de la liste des fichiers HF pour {path_to_repo}: {e}") | |
| return [], f"Erreur inconnue lors de l'accès à Hugging Face: {str(e)}" |