Skip to content

Commit

Permalink
common: general improvements
Browse files Browse the repository at this point in the history
- if timeout happens we try 5 times before
  sending Timeout error to users.

- improve user experience when timeout occurs

- Added console source tree for handling messages

Resolves: #693
Signed-off-by: Douglas Schilling Landgraf <[email protected]>
  • Loading branch information
dougsland committed Feb 2, 2025
1 parent c775844 commit 2f44162
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 14 deletions.
74 changes: 60 additions & 14 deletions ramalama/common.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
"""ramalama common module."""

import glob
import hashlib
import os
import random
import logging
import shutil
import string
import subprocess
import time
import sys
import urllib.request
import urllib.error
import ramalama.console as console

from ramalama.http_client import HttpClient


logging.basicConfig(
level=logging.WARNING,
format="%(asctime)s - %(levelname)s - %(message)s"
)

MNT_DIR = "/mnt/models"
MNT_FILE = f"{MNT_DIR}/model.file"
HTTP_RANGE_NOT_SATISFIABLE = 416


def container_manager():
Expand Down Expand Up @@ -165,25 +175,61 @@ def download_file(url, dest_path, headers=None, show_progress=True):
headers (dict): Optional headers to include in the request.
show_progress (bool): Whether to show a progress bar during download.
Returns:
None
Raises:
RuntimeError: If the download fails after multiple attempts.
"""
http_client = HttpClient()

headers = headers or {}

# if we are not a tty, don't show progress, can pollute CI output and such
# If not running in a TTY, disable progress to prevent CI pollution
if not sys.stdout.isatty():
show_progress = False

try:
http_client.init(url=url, headers=headers, output_file=dest_path, progress=show_progress)
except urllib.error.HTTPError as e:
if e.code == 416: # Range not satisfiable
if show_progress:
print(f"File {url} already fully downloaded.")
else:
raise e
http_client = HttpClient()
max_retries = 5 # Stop after 5 failures
retries = 0

while retries < max_retries:
try:
# Initialize HTTP client for the request
http_client.init(url=url, headers=headers, output_file=dest_path, progress=show_progress)
return # Exit function if successful

except urllib.error.HTTPError as e:
if e.code == HTTP_RANGE_NOT_SATISFIABLE: # "Range Not Satisfiable" error (file already downloaded)
return # No need to retry

except urllib.error.URLError as e:
console.error(f"Network Error: {e.reason}")
retries += 1

except TimeoutError:
retries += 1
console.warning(f"TimeoutError: The server took too long to respond. Retrying {retries}/{max_retries}...")

except RuntimeError as e: # Catch network-related errors from HttpClient
retries += 1
console.warning(f"{e}. Retrying {retries}/{max_retries}...")

except IOError as e:
retries += 1
console.warning(f"I/O Error: {e}. Retrying {retries}/{max_retries}...")

except Exception as e:
console.error(f"Unexpected error: {str(e)}")
raise

if retries >= max_retries:
error_message = (
"\nDownload failed after multiple attempts.\n"
"Possible causes:\n"
"- Internet connection issue\n"
"- Server is down or unresponsive\n"
"- Firewall or proxy blocking the request\n"
)
console.error(error_message)
sys.exit(1)

time.sleep(2 ** retries * 0.1) # Exponential backoff (0.1s, 0.2s, 0.4s...)


def engine_version(engine):
Expand Down
53 changes: 53 additions & 0 deletions ramalama/console.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""
MIT License
(C) 2024-2025 ramalama developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
"""
import os
import sys
import locale
import logging

def is_locale_utf8():
"""Check if the system locale is UTF-8."""
lang = os.getenv("LC_CTYPE", "") or os.getenv("LANG", "")
return "UTF-8" in lang.upper() or "utf8" in lang.lower()

def supports_emoji():
"""Detect if the terminal supports emoji output."""
if not is_locale_utf8():
return False # Block emoji if UTF-8 is not in locale

if sys.platform == "win32": # works 32, 64 bits machines
# Windows Terminal, ConEmu, VSCode, and WSL support emoji
if os.getenv("WT_SESSION") or os.getenv("ConEmuANSI") or os.getenv("TERM_PROGRAM") == "vscode":
return True
return False # Legacy cmd.exe and PowerShell lack proper emoji support

return True # Most Linux/macOS terminals support emoji

# Allow users to override emoji support via an environment variable
EMOJI = os.getenv("RAMALAMA_FORCE_EMOJI", "1") != "0" and supports_emoji()

# Define emoji-aware logging messages
def error(msg):
formatted_msg = f"❌ {msg}" if EMOJI else f"[ERROR] {msg}"
logging.error(formatted_msg)

def warning(msg):
formatted_msg = f"⚠️ {msg}" if EMOJI else f"[WARNING] {msg}"
logging.warning(formatted_msg)

def info(msg):
formatted_msg = f"ℹ️ {msg}" if EMOJI else f"[INFO] {msg}"
logging.info(formatted_msg)

0 comments on commit 2f44162

Please sign in to comment.