Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,26 @@ except e:

assert raised
```

Delegation can also be called multiple times on the same class, and there is an
optional `prefix` option which allows the attribute name to be prefixed on the
delegating class:

```python
from delegate import delegate

class Parent:
def __init__(self):
self.a = "a"
self.b = "b"

@delegate("a", to="parent")
@delegate("b", to="parent", prefix="_")
class Child:
def __init__(self):
self.parent = Parent()
self.c = "c"

instance.a
instance._b
```
38 changes: 24 additions & 14 deletions delegate/__init__.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
#!/usr/bin/env python3

def bind(instance, func, name):
"""Turn a function into a bound method on instance"""
setattr(instance, name, func.__get__(instance, instance.__class__))

def delegate(*args, **named_args):
dest = named_args.get('to')
if dest is None:
_dest = named_args.get('to')
if _dest is None:
raise ValueError(
"the named argument 'to' is required on the delegate function")

_prefix = named_args.get('prefix', '')

print(_dest, _prefix)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This print call should be removed


def wraps(cls, *wrapped_args, **wrapped_opts):
"""Wrap the target class up in something that modifies."""
class Wrapped(cls):

_delegates = [(a, _dest, _prefix) for a in args] + getattr(cls, '_delegates', [])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clever


def __getattr__(self, name):
"""
Return the selected name from the destination if the name is one
Expand All @@ -21,27 +24,34 @@ def __getattr__(self, name):
error when `name` is not one of the selected args to be
delegated.
"""
if name in args: return getattr(self.__dict__[dest], name)
for attr, dest, prefix in self._delegates:
if name == prefix + attr:
return getattr(self.__dict__[dest], name[len(prefix):])

raise AttributeError(f"'{self.__class__.__name__}' has no attribute '{name}'")

def __setattr__(self, name, value):
"""
If this name is one of those selected, set it on the destination
property. Otherwise, set it on self.
"""
if name in args: setattr(getattr(self, dest), name, value)
else: self.__dict__[name] = value
for attr, dest, prefix in self._delegates:
if name == prefix + attr:
setattr(getattr(self, dest), name[len(prefix):], value)
return
self.__dict__[name] = value

def __delattr__(self, name):
"""Delete name from `dest` or `self`"""
if name in args: delattr(getattr(self, dest), name)
else: del self.__dict__[name]
for attr, dest, prefix in self._delegates:
if name == prefix + attr:
delattr(getattr(self, dest), name[len(prefix):])
return

def __init__(self, *wrapped_args, **wrapped_opts):
super().__init__(*wrapped_args, **wrapped_opts)
Comment on lines -40 to -41
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this not necessary?

del self.__dict__[name]

Wrapped.__doc__ = cls.__doc__ or \
f"{cls.__class__} wrapped to delegate {args} to its {dest} property"
f"{cls.__class__} wrapped to delegate {args} to its {_dest} property"
Wrapped.__repr__ = cls.__repr__
Wrapped.__str__ = cls.__str__
Wrapped.__name__ = cls.__name__
Expand Down
6 changes: 6 additions & 0 deletions test-delegate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ class Parent:
def __init__(self):
self.a = "a"
self.b = "b"
self.d = "d"

@delegate("a", "b", to="parent")
@delegate("d", to="parent", prefix="_")
class Child:
"""A class with a delegate"""
def __init__(self):
Expand Down Expand Up @@ -41,5 +43,9 @@ def expect_raises(errtype):
assert instance.__class__.__name__ == "Child"
with expect_raises(AttributeError):
instance.z
with expect_raises(AttributeError):
instance.d
instance._d
assert len(Child._delegates) == 3

print("TESTING delegate()...done")