Beyond the basics¶
delegatee class¶
Instead of using an iterable in the delegates dictionary, it is possible to pass a delegatee instance as a value.
This will yield more flexibility and features when decide to forward class attributes and methods.
Let's see in detail which features are currently available.
Features¶
Attributes and methods validation¶
When using delegatee class we have the option to check whether or not attributes and methods are present in the class.
If we do so (validate=True parameter), then an AttributeError is raised if the attribute/method is not found in the class definition.
Remark that we check for:
- class attributes and methods;
- instance attributes assigned in the
__init__method, by parsing the__init__code and look forself.attr_name = ...syntax.
Why should you check if an attribute/method is present?
A validation step makes sure that changing something from the component class doesn't break the class using the given component somewhere down the rabbit hole, but it gets detected as soon as possible.
The "star" argument¶
Sometimes you want to forward all the methods of a given class. To accomplish that, it is enough to pass attrs=["*"] to the delegatee class.
Doing so will parse and add all the class attributes, methods and instance attributes of the delegatee_cls to the composed class.
This gives the possibility to forward all the methods/attributes of a component with a single instruction.
Warning
Dunder methods and instance attributes are not forwarded unless explicitly specified in the attrs parameter.
Custom prefix & suffix¶
It is also possible to specify custom prefix and/or suffix for each component. The default behaviour is to call it as in the delegatee class, but it is possible to change that.
Warning
Dunder methods ignore the prefix and suffix parameters.
Verbosity¶
compclass and CompclassMeta accept a verbose parameter which defines the level of verbosity when setting those forwarded methods.
If the value is True then we explicitly "declare" each forwarded method/attribute.
Examples¶
As in the previous section let's define the Foo and Bar classes:
class Foo:
"""Foo class"""
def __init__(self, value: int):
"""foo init"""
self._value = value
def get_value(self):
"""get value attribute"""
return self._value
def hello(self, name: str) -> str:
"""Method with argument"""
return f"Hello {name}, this is Foo!"
class Bar:
"""Bar class"""
b: int = 1
def __len__(self) -> int:
"""Custom len method"""
return 42
Now instead of using a list of attribute/methods name, let's define delegates using delegatee class, with few extra params:
from compclasses import compclass, delegatee
delegates = {
"_foo": delegatee(
delegatee_cls=Foo, # class definition of the _foo instance
attrs=("*", ), # list of attributes/methods to forward
suffix="_from_foo" # let's add a prefix to distinguish from where the method is forwarded, this can be any string
),
"_bar": delegatee(
delegatee_cls=Bar,
attrs=("__len__", "b"),
validate=True, # we want to validate that Bar class has "__len__" method and "b" attribute
prefix="bar_" # let's add a prefix to distinguish from where the method is forwarded, this can be any string
)
}
@compclass(
delegates=delegates,
verbose=True # verbisity level to use
)
class Baz:
"""Baz class"""
def __init__(self, foo: Foo, bar: Bar):
self._foo = foo
self._bar = bar
# Unable to parse __init__ method of <class '__main__.Bar'> due to the following reason: module, class, method, function, traceback, frame, or code object was expected, got wrapper_descriptor
# Setting get_value_from_foo from _foo.get_value
# Setting hello_from_foo from _foo.hello
# Setting _value_from_foo from _foo._value
# Setting __len__ from _bar.__len__
# Setting bar_b from _bar.b
Let's see what is happening here:
Bar'sdelegateehavevalidate=Trueparam, therefore checks__len__andbare presents. While doing so it fails to find an__init__method (this is an implementation detail of the classobjectimplemented in C). Notice that however this does not raise an error. It would have if any of__len__orbwere not found.- Passing
attrs=("*", )forFooallows to forward all non-dunder methods ofFootoBaz, namelyget_value,helloand_value(this last one is found inFoo.__init__). - Since we are using a suffix, the new methods in
Bazare calledget_value_from_foo,hello_from_fooand_value_from_foo. - Similarly we use a prefix in
Bardelegatee, hencebbecomesbar_b, yet__len__is forwarded as-is.
Let's now see what happens if a class attribute or an undefined method is passed in the attrs list:
delegatee(delegatee_cls=Foo, attrs=("_value",))
delegatee(delegatee_cls=Foo, attrs=("some_fake_method",))
(...) AttributeError: '
' has no attribute nor method 'some_fake_method'
In the first case, _value attribute can be detected from the __init__ method as we saw above already.
For the latter case, it is clear that the method is not present in the class definition, and an error is raised.