"""
generic.py - Base wrappers for plugin data structures
=====================================================
This module provides generic Python wrapper classes for plugin data structures, such as those returned by PANDA or other emulation/analysis frameworks. These wrappers provide a uniform interface for accessing fields, converting to dicts, and working with arrays of wrapped objects.
Overview
--------
The main classes are:
- Wrapper: A base class for wrapping a single object, providing attribute access, dict conversion, and pretty-printing.
- ArrayWrapper: A base class for wrapping a list/array of objects, providing list-like access and iteration.
These classes are intended to be subclassed for specific data structures, but can also be used directly for simple cases.
Typical Usage
-------------
Suppose you have a plugin that returns a C struct or dict-like object:
.. code-block:: python
from wrappers.generic import Wrapper, ArrayWrapper
# Wrap a single object
data = plugin.get_struct()
obj = Wrapper(data)
print(obj.field1)
print(obj.to_dict())
# Wrap an array of objects
array_data = plugin.get_array()
objs = ArrayWrapper([Wrapper(x) for x in array_data])
for o in objs:
print(o)
Classes
-------
- Wrapper: Base class for single-object wrappers.
- ArrayWrapper: Base class for array/list wrappers.
"""
from typing import Any, Dict, Iterator, List, Sequence, TypeVar, Generic, Type
T = TypeVar('T')
[docs]
class Wrapper:
"""
Optimized base class for wrapping a single object.
Uses __slots__ and cached type flags to minimize __getattr__ overhead.
"""
__slots__ = ('_obj', '_extra_attrs', '_is_dict')
def __init__(self, obj: Any) -> None:
object.__setattr__(self, '_obj', obj)
object.__setattr__(self, '_extra_attrs', {})
object.__setattr__(self, '_is_dict', isinstance(obj, dict))
def __getattr__(self, name: str) -> Any:
# 1. Check extra_attrs (fastest local check)
extras = self._extra_attrs
if name in extras:
return extras[name]
# 2. Access the wrapped object based on cached type
obj = self._obj
if self._is_dict:
try:
return obj[name]
except KeyError:
# FIX: If 'name' isn't a data key, check if it's a dict method
# (e.g., .items(), .values(), .get())
return getattr(obj, name)
# 3. Standard object access for non-dicts
return getattr(obj, name)
def __setattr__(self, name: str, value: Any) -> None:
if name in ('_obj', '_extra_attrs', '_is_dict'):
object.__setattr__(self, name, value)
return
if self._is_dict:
self._obj[name] = value
else:
# Optimistic approach: try to set it on the object first.
# This is faster than 'hasattr' which internally does a get/catch.
try:
setattr(self._obj, name, value)
except AttributeError:
self._extra_attrs[name] = value
def __getitem__(self, key: str) -> Any:
return self.__getattr__(key)
def __dir__(self) -> List[str]:
base = list(self._extra_attrs.keys())
# Dicts don't support dir() well for keys, handle separately
if self._is_dict:
return base + list(self._obj.keys())
return base + dir(self._obj)
[docs]
def to_dict(self) -> Dict[str, Any]:
if self._is_dict:
return dict(self._obj)
elif hasattr(self._obj, '__dict__'):
return dict(self._obj.__dict__)
else:
return {k: getattr(self._obj, k)
for k in dir(self._obj) if not k.startswith('_')}
def __repr__(self) -> str:
return f"{self.__class__.__name__}({repr(self._obj)})"
def __str__(self) -> str:
return str(self._obj)
[docs]
class ArrayWrapper(Generic[T]):
"""
Optimized base class for wrapping a list/array of objects.
Performs LAZY wrapping: objects are only wrapped when accessed.
"""
def __init__(self, data: Sequence[Any], wrapper_cls: Type[T] = Wrapper) -> None:
self._data = data
self._wrapper_cls = wrapper_cls
def __getitem__(self, idx: int) -> T:
# Wrap on demand!
return self._wrapper_cls(self._data[idx])
def __len__(self) -> int:
return len(self._data)
def __iter__(self) -> Iterator[T]:
# Generator expression for lazy iteration
return (self._wrapper_cls(x) for x in self._data)
[docs]
def to_list(self) -> List[T]:
return [self._wrapper_cls(x) for x in self._data]
def __repr__(self) -> str:
return f"{self.__class__.__name__}({repr(self._data)})"
def __str__(self) -> str:
return str(self._data)
[docs]
class ConstDictWrapper:
"""
A read-only wrapper for dictionaries optimized for maximum read speed.
Attributes are injected directly into the instance's __dict__ at
initialization time, allowing for native C-speed attribute access
(obj.field) without the overhead of __getattr__.
"""
def __init__(self, data: Dict[str, Any]) -> None:
"""
Initialize the wrapper.
This performs a shallow copy of the dictionary keys into the
object's namespace.
"""
# DIRECT INJECTION: This is the magic speed trick.
# By updating __dict__ directly, we bypass __setattr__ logic
# and populate the attributes immediately.
self.__dict__.update(data)
def __setattr__(self, name: str, value: Any) -> None:
"""Block attempts to change values or add new attributes."""
raise TypeError(f"'{self.__class__.__name__}' object is immutable")
def __delattr__(self, name: str) -> None:
"""Block attempts to delete attributes."""
raise TypeError(f"'{self.__class__.__name__}' object is immutable")
def __getitem__(self, key: str) -> Any:
"""Allow dictionary-like access (wrapper['key'])."""
return self.__dict__[key]
def __setitem__(self, key: str, value: Any) -> None:
"""Block dictionary-like assignment."""
raise TypeError(f"'{self.__class__.__name__}' object is immutable")
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.__dict__})"
[docs]
def to_dict(self) -> Dict[str, Any]:
"""Return the underlying data as a dict."""
# Since our __dict__ IS the data, we just return a copy of it.
return self.__dict__.copy()
[docs]
def items(self):
"""Pass-through for dict iteration."""
return self.__dict__.items()
[docs]
def keys(self):
"""Pass-through for keys."""
return self.__dict__.keys()
[docs]
def values(self):
"""Pass-through for values."""
return self.__dict__.values()