Skip to content

Commit

Permalink
adding lazy attribute lookup on publishers
Browse files Browse the repository at this point in the history
  • Loading branch information
semiversus committed Sep 30, 2018
1 parent 59409b1 commit 2f7dbe9
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 1 deletion.
12 changes: 12 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,18 @@ Some python built in functions can't return Publishers (e.g. ``len()`` needs to
return an integer). For this cases special functions are defined in broqer: ``Str``,
``Int``, ``Float``, ``Len`` and ``In`` (for ``x in y``).

Attribute access on a publisher is building a publisher where the actual attribute
access is done on emitting values:

.. code-block:: python3
>>> i = Value('Attribute access made REACTIVE')
>>> i.lower().strip(sep=' ') | op.Sink(print)
['attribute', 'access', 'made', 'reactive']
>>> i.emit('Reactive and pythonic')
['reactive', 'and', 'pythonic']
Asyncio Support
---------------

Expand Down
36 changes: 36 additions & 0 deletions broqer/op/operator_overloading.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,37 @@ def emit(self, value: Any, who: Publisher) -> asyncio.Future:
return self.notify(result)


class _GetAttr(Operator):
def __init__(self, publisher: Publisher, attribute_name) -> None:
Operator.__init__(self)
self._attribute_name = attribute_name
self._publisher = publisher
self._args = None
self._kwargs = None

def get(self):
value = self._publisher.get() # may raise ValueError
attribute = getattr(value, self._attribute_name)
if self._args is None:
return attribute
return attribute(*self._args, **self._kwargs)

def __call__(self, *args, **kwargs):
self._args = args
self._kwargs = kwargs
return self

def emit(self, value: Any, who: Publisher) -> asyncio.Future:
assert who is self._publisher, 'emit from non assigned publisher'

attribute = getattr(value, self._attribute_name)

if self._args is None:
return self.notify(attribute)

return self.notify(attribute(*self._args, **self._kwargs))


def apply_operator_overloading():
""" Function to apply operator overloading to Publisher class """
# operator overloading is (unfortunately) not working for the following
Expand Down Expand Up @@ -107,6 +138,11 @@ def _op_unary(operand, operation=_method):

setattr(Publisher, method, _op_unary)

def _getattr(publisher, method_name):
return _GetAttr(publisher, method_name)

setattr(Publisher, '__getattr__', _getattr)


class Str(_MapUnary):
""" Implementing the functionality of str() for publishers. """
Expand Down
61 changes: 60 additions & 1 deletion test/test_core_publisher_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,4 +297,63 @@ def test_in_operator():

assert dut1.get() == False
assert dut2.get() == False
assert dut3.get() == False
assert dut3.get() == False

def test_getattr_method():
p = StatefulPublisher()

dut1 = p.split()
dut2 = p.split(',')
dut3 = p.split(sep = '!')

mock1 = mock.Mock()
mock2 = mock.Mock()
mock3 = mock.Mock()

dut1 | op.Sink(mock1)
dut2 | op.Sink(mock2)
dut3 | op.Sink(mock3)

with pytest.raises(ValueError):
dut1.get()

with pytest.raises(ValueError):
dut2.get()

with pytest.raises(ValueError):
dut3.get()

mock1.assert_not_called()
mock2.assert_not_called()
mock3.assert_not_called()

p.notify('This is just a test, honestly!')

assert dut1.get() == ['This', 'is', 'just', 'a', 'test,', 'honestly!']
assert dut2.get() == ['This is just a test', ' honestly!']
assert dut3.get() == ['This is just a test, honestly', '']

mock1.assert_called_once_with(['This', 'is', 'just', 'a', 'test,', 'honestly!'])
mock2.assert_called_once_with(['This is just a test', ' honestly!'])
mock3.assert_called_once_with(['This is just a test, honestly', ''])

def test_getattr_attribute():
p = StatefulPublisher()
class Foo:
def __init__(self, a=5):
self.a = a

dut = p.a
m = mock.Mock()
dut | op.Sink(m)

with pytest.raises(ValueError):
dut.get()

m.assert_not_called()

p.notify(Foo(3))

assert dut.get() == 3

m.assert_called_once_with(3)

0 comments on commit 2f7dbe9

Please sign in to comment.