odoo18/odoo/tools/facade.py

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__