Source code for scitex_io._utils

#!/usr/bin/env python3
"""
Inline utilities to avoid external dependencies.
All utilities needed by scitex-io that would otherwise come from scitex.
"""

import os
from pathlib import Path


# String utilities
def clean_path(path_string):
    """Clean and normalize a file system path."""
    return os.path.normpath(str(path_string))


def color_text(text, color):
    """Simple colored text."""
    try:
        from colorama import Fore, Style

        colors = {
            "green": Fore.GREEN,
            "red": Fore.RED,
            "yellow": Fore.YELLOW,
            "blue": Fore.BLUE,
            "magenta": Fore.MAGENTA,
            "cyan": Fore.CYAN,
        }
        return f"{colors.get(color, '')}{text}{Style.RESET_ALL}"
    except ImportError:
        return text


def readable_bytes(size):
    """Convert bytes to human readable format."""
    for unit in ["B", "KB", "MB", "GB", "TB"]:
        if size < 1024.0:
            return f"{size:.2f} {unit}"
        size /= 1024.0
    return f"{size:.2f} PB"


# Dict utilities
[docs] class DotDict: """A dictionary-like object that allows attribute-like access (for valid identifier keys) and standard item access for all keys (including integers, etc.)."""
[docs] def __init__(self, dictionary=None): super().__setattr__("_data", {}) if dictionary is not None: if isinstance(dictionary, DotDict): dictionary = dictionary._data elif not isinstance(dictionary, dict): raise TypeError("Input must be a dictionary.") for key, value in dictionary.items(): if isinstance(value, dict) and not isinstance(value, DotDict): value = DotDict(value) self[key] = value
def __getattr__(self, key): if key.startswith("_"): return super().__getattribute__(key) try: return self._data[key] except KeyError: raise AttributeError( f"'{type(self).__name__}' object has no attribute '{key}'" ) def __setattr__(self, key, value): if key == "_data" or key.startswith("_"): super().__setattr__(key, value) else: if isinstance(value, dict) and not isinstance(value, DotDict): value = DotDict(value) self._data[key] = value def __delattr__(self, key): if key.startswith("_"): super().__delattr__(key) else: try: del self._data[key] except KeyError: raise AttributeError( f"'{type(self).__name__}' object has no attribute '{key}'" ) def __getitem__(self, key): return self._data[key] def __setitem__(self, key, value): if isinstance(value, dict) and not isinstance(value, DotDict): value = DotDict(value) self._data[key] = value def __delitem__(self, key): del self._data[key]
[docs] def get(self, key, default=None): return self._data.get(key, default)
[docs] def to_dict(self, include_private=False): """Recursively convert to plain dict.""" result = {} for key, value in self._data.items(): if not include_private and isinstance(key, str) and key.startswith("_"): continue if isinstance(value, DotDict): value = value.to_dict(include_private=include_private) result[key] = value return result
def __str__(self): import json as _json def default_handler(obj): if isinstance(obj, DotDict): return obj.to_dict() try: _json.dumps(obj) return obj except (TypeError, OverflowError): return str(obj) try: return _json.dumps(self.to_dict(), indent=4, default=default_handler) except TypeError as e: return f"<DotDict at {hex(id(self))}, keys: {list(self._data.keys())}> Error: {e}" def __repr__(self): import pprint as _pprint return _pprint.pformat(self.to_dict(include_private=False), indent=2, width=80) def __len__(self): return len(self._data)
[docs] def keys(self): return self._data.keys()
[docs] def values(self): return self._data.values()
[docs] def items(self): return self._data.items()
[docs] def update(self, dictionary): if isinstance(dictionary, dict): iterator = dictionary.items() elif hasattr(dictionary, "__iter__"): iterator = dictionary else: raise TypeError( "Input must be a dictionary or an iterable of key-value pairs." ) for key, value in iterator: self[key] = value
[docs] def setdefault(self, key, default=None): if key not in self._data: self[key] = default return default return self._data[key]
[docs] def pop(self, key, *args): if len(args) > 1: raise TypeError(f"pop expected at most 2 arguments, got {1 + len(args)}") if key not in self._data: if args: return args[0] raise KeyError(key) return self._data.pop(key)
def __contains__(self, key): return key in self._data def __iter__(self): return iter(self._data)
[docs] def copy(self): return DotDict(self._data.copy())
def __eq__(self, other): if isinstance(other, DotDict): return self._data == other._data elif isinstance(other, dict): return self._data == other return False def __ne__(self, other): return not self.__eq__(other) def __bool__(self): return len(self._data) > 0
# Decorator utilities def preserve_doc(func): """Placeholder for preserve_doc decorator.""" return func # Path utilities def split(path): """Split path into components.""" return Path(path).parts def this_path(): """Get current file path.""" import inspect frame = inspect.currentframe().f_back return frame.f_code.co_filename def clean(path): """Clean path.""" return str(Path(path).resolve()) def getsize(path): """Get file size in bytes.""" return Path(path).stat().st_size if Path(path).exists() else 0 # String parsing def parse(string, pattern=None): """Simple string parser.""" if pattern is None: return string import re return re.findall(pattern, string) # Environment detection def detect_environment(): """Detect execution environment.""" try: get_ipython() # type: ignore return "jupyter" except NameError: return "python" def get_notebook_info_simple(): """Get notebook info.""" return {"path": None, "name": None}