124 lines
4.3 KiB
Python
124 lines
4.3 KiB
Python
import functools
|
|
import inspect
|
|
|
|
|
|
class ProxyAttr:
|
|
"""
|
|
Descriptor class for wrapping attributes of the wrapped instance.
|
|
|
|
Used with the `Proxy` class, this class is used to set exposed attributes of the wrapped instance while providing
|
|
optional type casting.
|
|
"""
|
|
def __init__(self, cast=False):
|
|
self._cast__ = cast
|
|
|
|
def __set_name__(self, owner, name):
|
|
cast = self._cast__
|
|
if cast:
|
|
def getter(self):
|
|
value = getattr(self._wrapped__, name)
|
|
return cast(value) if value is not None else None
|
|
else:
|
|
def getter(self):
|
|
return getattr(self._wrapped__, name)
|
|
|
|
def setter(self, value):
|
|
return setattr(self._wrapped__, name, value)
|
|
|
|
setattr(owner, name, property(getter, setter))
|
|
|
|
|
|
class ProxyFunc:
|
|
"""
|
|
Descriptor class for wrapping functions of the wrapped instance.
|
|
|
|
Used with the `Proxy` class, this class is used to set exposed functions of the wrapped instance
|
|
while also allowing optional type casting on return values.
|
|
"""
|
|
def __init__(self, cast=False):
|
|
self._cast__ = cast
|
|
|
|
def __set_name__(self, owner, name):
|
|
func = getattr(owner._wrapped__, name)
|
|
descriptor = inspect.getattr_static(owner._wrapped__, name)
|
|
cast = self._cast__
|
|
|
|
if isinstance(descriptor, staticmethod):
|
|
if cast:
|
|
def wrap_func(*args, **kwargs):
|
|
result = func(*args, **kwargs)
|
|
return cast(result) if result is not None else None
|
|
elif cast is None:
|
|
def wrap_func(*args, **kwargs):
|
|
func(*args, **kwargs)
|
|
else:
|
|
def wrap_func(*args, **kwargs):
|
|
return func(*args, **kwargs)
|
|
|
|
functools.update_wrapper(wrap_func, func)
|
|
wrap_func = staticmethod(wrap_func)
|
|
|
|
elif isinstance(descriptor, classmethod):
|
|
if cast:
|
|
def wrap_func(cls, *args, **kwargs):
|
|
result = func(*args, **kwargs)
|
|
return cast(result) if result is not None else None
|
|
elif cast is None:
|
|
def wrap_func(cls, *args, **kwargs):
|
|
func(*args, **kwargs)
|
|
else:
|
|
def wrap_func(cls, *args, **kwargs):
|
|
return func(*args, **kwargs)
|
|
|
|
functools.update_wrapper(wrap_func, func)
|
|
wrap_func = classmethod(wrap_func)
|
|
|
|
else:
|
|
if cast:
|
|
def wrap_func(self, *args, **kwargs):
|
|
result = func(self._wrapped__, *args, **kwargs)
|
|
return cast(result) if result is not None else None
|
|
elif cast is None:
|
|
def wrap_func(self, *args, **kwargs):
|
|
func(self._wrapped__, *args, **kwargs)
|
|
else:
|
|
def wrap_func(self, *args, **kwargs):
|
|
return func(self._wrapped__, *args, **kwargs)
|
|
|
|
functools.update_wrapper(wrap_func, func)
|
|
|
|
setattr(owner, name, wrap_func)
|
|
|
|
|
|
class ProxyMeta(type):
|
|
def __new__(cls, clsname, bases, attrs):
|
|
attrs.update({func: ProxyFunc() for func in ("__repr__", "__str__") if func not in attrs})
|
|
proxy_class = super().__new__(cls, clsname, bases, attrs)
|
|
# To preserve the docstring, signature, code of the wrapped class
|
|
# `updated` to an emtpy list so it doesn't copy the `__dict__`
|
|
# See `functools.WRAPPER_ASSIGNMENTS` and `functools.WRAPPER_UPDATES`
|
|
functools.update_wrapper(proxy_class, proxy_class._wrapped__, updated=[])
|
|
return proxy_class
|
|
|
|
|
|
class Proxy(metaclass=ProxyMeta):
|
|
"""
|
|
A proxy class implementing the Facade pattern.
|
|
|
|
This class delegates to an underlying instance while exposing a curated subset of its attributes and methods.
|
|
Useful for controlling access, simplifying interfaces, or adding cross-cutting concerns.
|
|
"""
|
|
_wrapped__ = object
|
|
|
|
def __init__(self, instance):
|
|
"""
|
|
Initializes the proxy by setting the wrapped instance.
|
|
|
|
:param instance: The instance of the class to be wrapped.
|
|
"""
|
|
object.__setattr__(self, "_wrapped__", instance)
|
|
|
|
@property
|
|
def __class__(self):
|
|
return type(self)._wrapped__
|