PYTHON
utils.py🐍python
"""
Utility functions for password manager.
"""
import sys
import time
import threading
from typing import Optional, Callable
from functools import wraps
# Try to import clipboard
try:
import pyperclip
HAS_CLIPBOARD = True
except ImportError:
HAS_CLIPBOARD = False
def copy_to_clipboard(text: str, clear_after: int = 30) -> bool:
"""
Copy text to clipboard and optionally clear after timeout.
Args:
text: Text to copy
clear_after: Seconds before clearing (0 to disable)
Returns:
True if successful
"""
if not HAS_CLIPBOARD:
return False
try:
pyperclip.copy(text)
if clear_after > 0:
# Start timer to clear clipboard
def clear():
time.sleep(clear_after)
try:
current = pyperclip.paste()
if current == text:
pyperclip.copy("")
except:
pass
thread = threading.Thread(target=clear, daemon=True)
thread.start()
return True
except:
return False
def clear_clipboard():
"""Clear the clipboard."""
if HAS_CLIPBOARD:
try:
pyperclip.copy("")
except:
pass
def get_from_clipboard() -> str:
"""Get text from clipboard."""
if not HAS_CLIPBOARD:
return ""
try:
return pyperclip.paste()
except:
return ""
def secure_input(prompt: str = "", hide: bool = True) -> str:
"""
Get secure input from user (hidden for passwords).
Args:
prompt: Prompt to display
hide: Whether to hide input
Returns:
User input
"""
if hide:
try:
import getpass
return getpass.getpass(prompt)
except:
pass
return input(prompt)
def confirm(prompt: str, default: bool = False) -> bool:
"""
Ask user for confirmation.
Args:
prompt: Question to ask
default: Default answer
Returns:
User's choice
"""
suffix = " [Y/n]" if default else " [y/N]"
while True:
response = input(prompt + suffix + ": ").strip().lower()
if not response:
return default
if response in ("y", "yes"):
return True
if response in ("n", "no"):
return False
print("Please answer 'yes' or 'no'")
def format_time_ago(timestamp: str) -> str:
"""
Format timestamp as relative time.
Args:
timestamp: ISO format timestamp
Returns:
Human-readable relative time
"""
from datetime import datetime
try:
dt = datetime.fromisoformat(timestamp)
now = datetime.utcnow()
delta = now - dt
seconds = delta.total_seconds()
if seconds < 60:
return "just now"
minutes = seconds / 60
if minutes < 60:
return f"{int(minutes)} minutes ago"
hours = minutes / 60
if hours < 24:
return f"{int(hours)} hours ago"
days = hours / 24
if days < 30:
return f"{int(days)} days ago"
months = days / 30
if months < 12:
return f"{int(months)} months ago"
years = months / 12
return f"{int(years)} years ago"
except:
return timestamp
def mask_password(password: str, show_length: bool = True) -> str:
"""
Mask a password for display.
Args:
password: Password to mask
show_length: Whether to indicate length
Returns:
Masked string
"""
if show_length:
return "*" * min(len(password), 16) + f" ({len(password)} chars)"
return "********"
def truncate(text: str, max_length: int = 50, suffix: str = "...") -> str:
"""
Truncate text to max length.
Args:
text: Text to truncate
max_length: Maximum length
suffix: Suffix to add if truncated
Returns:
Truncated text
"""
if len(text) <= max_length:
return text
return text[:max_length - len(suffix)] + suffix
def rate_limit(calls: int, period: float) -> Callable:
"""
Decorator to rate limit function calls.
Args:
calls: Maximum calls allowed
period: Time period in seconds
Returns:
Decorator function
"""
def decorator(func: Callable) -> Callable:
timestamps = []
lock = threading.Lock()
@wraps(func)
def wrapper(*args, **kwargs):
with lock:
now = time.time()
# Remove old timestamps
timestamps[:] = [t for t in timestamps if now - t < period]
if len(timestamps) >= calls:
raise RuntimeError(
f"Rate limit exceeded: {calls} calls per {period}s"
)
timestamps.append(now)
return func(*args, **kwargs)
return wrapper
return decorator
def retry(max_attempts: int = 3, delay: float = 1.0) -> Callable:
"""
Decorator to retry function on failure.
Args:
max_attempts: Maximum retry attempts
delay: Delay between retries
Returns:
Decorator function
"""
def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs):
last_error: Exception = RuntimeError("No attempts made")
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
last_error = e
if attempt < max_attempts - 1:
time.sleep(delay)
raise last_error
return wrapper
return decorator