Files
zeekay ea3116aac9 Fix all test issues and achieve 100% test pass rate
- Remove all unconditional pytest.skip() calls
- Fix file content tests to handle both strict/non-strict modes
- Fix missing device_capabilities module in hanzo-network
- Fix HanzoNetProvider imports and exports
- Fix local_llm.py import errors
- Create test helper classes for pagination tests
- Fix FastMCP paginator test constructor args
- Configure all API keys from ~/.zsh/after.zsh
- All tests now pass or skip conditionally based on actual requirements
2025-08-12 16:19:34 -05:00

120 lines
3.9 KiB
Python

"""Encoding management for file operations."""
import functools
import os
from pathlib import Path
from typing import Tuple
import charset_normalizer
from cachetools import LRUCache
class EncodingManager:
"""Manages file encodings across multiple operations to ensure consistency."""
# Default maximum number of entries in the cache
DEFAULT_MAX_CACHE_SIZE = 1000 # ~= 300 KB
def __init__(self, max_cache_size=None):
# Cache detected encodings to avoid repeated detection on the same file
# Format: {path_str: (encoding, mtime)}
self._encoding_cache: LRUCache[str, Tuple[str, float]] = LRUCache(
maxsize=max_cache_size or self.DEFAULT_MAX_CACHE_SIZE
)
# Default fallback encoding
self.default_encoding = "utf-8"
# Confidence threshold for encoding detection
self.confidence_threshold = 0.9
def detect_encoding(self, path: Path) -> str:
"""Detect the encoding of a file without handling caching logic.
Args:
path: Path to the file
Returns:
The detected encoding or default encoding if detection fails
"""
# Handle non-existent files
if not path.exists():
return self.default_encoding
# Read a sample of the file to detect encoding
sample_size = min(os.path.getsize(path), 1024 * 1024) # Max 1MB sample
with open(path, "rb") as f:
raw_data = f.read(sample_size)
# Use charset_normalizer instead of chardet
results = charset_normalizer.detect(raw_data)
# Get the best match if any exists
if results and results["confidence"] > self.confidence_threshold:
encoding = results["encoding"]
else:
encoding = self.default_encoding
return encoding
def get_encoding(self, path: Path) -> str:
"""Get encoding for a file, using cache or detecting if necessary.
Args:
path: Path to the file
Returns:
The encoding for the file
"""
path_str = str(path)
# If file doesn't exist, return default encoding
if not path.exists():
return self.default_encoding
# Get current modification time
current_mtime = os.path.getmtime(path)
# Check cache for valid entry
if path_str in self._encoding_cache:
cached_encoding, cached_mtime = self._encoding_cache[path_str]
if cached_mtime == current_mtime:
return cached_encoding
# No valid cache entry, detect encoding
encoding = self.detect_encoding(path)
# Cache the result with current modification time
self._encoding_cache[path_str] = (encoding, current_mtime)
return encoding
def with_encoding(method):
"""Decorator to handle file encoding for file operations.
This decorator automatically detects and applies the correct encoding
for file operations, ensuring consistency between read and write operations.
Args:
method: The method to decorate
Returns:
The decorated method
"""
@functools.wraps(method)
def wrapper(self, path: Path, *args, **kwargs):
# Skip encoding handling for directories
if path.is_dir():
return method(self, path, *args, **kwargs)
# For files that don't exist yet (like in 'create' command),
# use the default encoding
if not path.exists():
if "encoding" not in kwargs:
kwargs["encoding"] = self._encoding_manager.default_encoding
else:
# Get encoding from the encoding manager for existing files
encoding = self._encoding_manager.get_encoding(path)
# Add encoding to kwargs if the method accepts it
if "encoding" not in kwargs:
kwargs["encoding"] = encoding
return method(self, path, *args, **kwargs)
return wrapper