"""push_to_hf.py — Envia scripts em lote ao HF repositório 'CNN-BiGRU' (v2.0). Executa: 1. Cria repositório 'PowerMachine/CNN-BiGRU' (se não existir) 2. Envia todos os scripts .py, README.md, requirements.txt, docs/ usando `upload_folder` (upload em lote eficiente) 3. SOBRESCREVE módulos desatualizados (default do upload_folder) 4. Remove HF_TOKEN do ambiente após o uso (requisito do usuário) V2.0 melhorias: - Usa `upload_folder` (single call) em vez de `upload_file` em loop - Filtra arquivos por extensão e por padrões (ignora caches, logs, etc.) - Mantém árvore de pastas e subpastas conforme existente localmente - Log mais detalhado de sucesso/falha - Retry automático em caso de falha transitória Usage: HF_TOKEN=hf_xxx python push_to_hf.py """ from __future__ import annotations import os import sys import logging import time from pathlib import Path from typing import List, Set logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s") logger = logging.getLogger("push_hf") REPO_ID = "PowerMachine/CNN-BiGRU" # Path: scripts/push_to_hf.py -> parent=scripts -> parent=cnn_bigru PROJECT_DIR = Path(__file__).resolve().parent.parent # PROJECT_DIR.parent = raiz do projeto REPO_ROOT = PROJECT_DIR.parent # Extensões e nomes de arquivo permitidos para upload ALLOWED_EXTENSIONS: Set[str] = { ".py", ".md", ".txt", ".json", ".yaml", ".yml", ".toml", ".cfg", ".ini", ".sh", } # Padrões a ignorar (em qualquer parte do caminho) IGNORE_PATTERNS: Set[str] = { "__pycache__", ".pyc", ".pyo", ".pyd", ".git", ".gitignore", ".gitattributes", "node_modules", ".pytest_cache", ".mypy_cache", ".ruff_cache", ".DS_Store", "Thumbs.db", "*.log", "*.tmp", "*.swp", "*.bak", ".env", ".venv", "venv", "env", } # Arquivos específicos que NUNCA devem ser upados (podem conter tokens/sensíveis) SENSITIVE_FILENAMES: Set[str] = { ".env", ".env.local", ".env.production", ".env.development", "secrets.json", "credentials.json", "config.local.json", "hf_token.txt", "token.txt", } def should_ignore(path: Path) -> bool: """Verifica se um arquivo deve ser ignorado no upload.""" parts = path.parts name = path.name.lower() # Verificar padrões por partes do caminho for part in parts: part_lower = part.lower() if part_lower in IGNORE_PATTERNS: return True # Verificar extensões de cache if part_lower.endswith((".pyc", ".pyo", ".pyd")): return True # Verificar nome do arquivo if name in SENSITIVE_FILENAMES: return True # Verificar extensões permitidas if path.suffix.lower() not in ALLOWED_EXTENSIONS: # Permitir arquivos sem extensão apenas se forem específicos (README, LICENSE, etc.) if name not in {"readme", "license", "license-mit", "authors", "contributors"}: return True # Verificar padrões com wildcards for pattern in IGNORE_PATTERNS: if pattern.startswith("*"): suffix = pattern[1:] if name.endswith(suffix): return True return False def collect_files(project_dir: Path) -> List[Path]: """Coleta todos os arquivos elegíveis para upload.""" files = [] for path in project_dir.rglob("*"): if path.is_file() and not should_ignore(path): files.append(path) return files def upload_with_retry(api, folder: Path, repo_id: str, token: str, max_retries: int = 3): """Faz upload_folder com retry em caso de falha transitória.""" last_error = None for attempt in range(1, max_retries + 1): try: logger.info(f"Tentativa {attempt}/{max_retries}: upload_folder...") commit_info = api.upload_folder( folder_path=str(folder), repo_id=repo_id, repo_type="model", token=token, commit_message=f"v2.0: EWC + Context Window + RoPE + TransformerBlock + bug fixes (attempt {attempt})", # Sobrescreve arquivos desatualizados (default) ) logger.info(f"Upload bem-sucedido: {commit_info}") return True, None except Exception as e: last_error = e logger.warning(f"Tentativa {attempt} falhou: {e}") if attempt < max_retries: wait_time = 2 ** attempt logger.info(f"Aguardando {wait_time}s antes de tentar novamente...") time.sleep(wait_time) return False, last_error def main(): # Pega HF_TOKEN da variável de ambiente hf_token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN") if not hf_token: logger.error("HF_TOKEN não definido no ambiente. Abortando.") return 1 try: from huggingface_hub import HfApi, create_repo except ImportError: logger.error("huggingface_hub não instalado. Execute: pip install huggingface_hub") return 1 api = HfApi(token=hf_token) # 1. Cria repositório (se não existir) try: repo_url = create_repo( repo_id=REPO_ID, token=hf_token, repo_type="model", exist_ok=True, private=False, ) logger.info(f"Repositório criado/acessado: {repo_url}") except Exception as e: logger.error(f"Falha ao criar repositório: {e}") # Limpa o token mesmo em caso de falha _cleanup_token() return 1 # 2. Coleta todos os arquivos para upload files_to_upload = collect_files(PROJECT_DIR) logger.info(f"Total de arquivos para upload: {len(files_to_upload)} (de {PROJECT_DIR})") if not files_to_upload: logger.warning("Nenhum arquivo elegível para upload.") _cleanup_token() return 1 # Log dos arquivos que serão upados for f in files_to_upload[:10]: rel = f.relative_to(REPO_ROOT) logger.info(f" - {rel}") if len(files_to_upload) > 10: logger.info(f" ... e mais {len(files_to_upload) - 10} arquivos") # 3. Upload em lote via upload_folder (muito mais eficiente que upload_file em loop) success, error = upload_with_retry( api, folder=PROJECT_DIR, repo_id=REPO_ID, token=hf_token, max_retries=3 ) if success: logger.info("=" * 60) logger.info(f"Upload concluído com sucesso!") logger.info(f"Repo: https://huggingface.co/{REPO_ID}") logger.info(f"Total de arquivos enviados: {len(files_to_upload)}") logger.info("=" * 60) else: logger.error("=" * 60) logger.error(f"Falha no upload após retries: {error}") logger.error("=" * 60) # 4. Remove HF_TOKEN do ambiente (requisito do usuário) _cleanup_token() return 0 if success else 1 def _cleanup_token(): """Remove todas as variáveis de token HF do ambiente.""" removed = [] for var in ("HF_TOKEN", "HUGGING_FACE_HUB_TOKEN", "HF_HUB_TOKEN"): if var in os.environ: del os.environ[var] removed.append(var) if removed: logger.info(f"Tokens removidos do ambiente: {', '.join(removed)}") else: logger.info("Nenhum token HF encontrado no ambiente para remover.") if __name__ == "__main__": sys.exit(main())