Skip to content

Commit

Permalink
Added DynamicState pattern implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
jpic committed Mar 24, 2014
1 parent c6f1195 commit a641d24
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 2 deletions.
11 changes: 10 additions & 1 deletion docs/usage.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
Usage
~~~~~

:py:class:`signalslot.Signal` objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
=====================================

.. automodule:: signalslot.signal
:members:

:py:class:`signalslot.contrib.dynamic_state.DynamicState`
=========================================================

.. automodule:: signalslot.contrib.dynamic_state
:members:
Empty file added signalslot/contrib/__init__.py
Empty file.
59 changes: 59 additions & 0 deletions signalslot/contrib/dynamic_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from signalslot import Signal


class DynamicState(object):
"""
Base class for classes which require having dynamic states.
.. py:attribute:: fetch_attribute
Signal emited when a non-existent attribute is being accessed before
AttributeError is raised.
The dynamic state pattern attempts to solve a problem: having a property
fetched OAOO (Once And Only Once) by a decoupled module.
Subclass :py:class:`DynamicState` to implement the dynamic state pattern:
>>> class YourObject(DynamicState):
... pass
Consider the following function, provided by another module, which should
compute the initial value for a particular attribute ``foo``:
>>> def fetch_foo(obj, name, **kwargs):
... if name != 'foo':
... return # not my responsability
... return 'bar'
...
Connect that function to ``YourObject.fetch_attribute`` signal as usual:
>>> YourObject.fetch_attribute.connect(fetch_foo)
Now, the first time ``YourObject.foo`` is accessed, it will emit
``YourObject.fetch_attribute`` which will execute every slot until one of
them returns a non-None value:
>>> test = YourObject()
>>> assert test.foo == 'bar'
However, if no slots are connected, or each of them return None, then a
normal AttributeError is raised:
>>> import pytest
>>> with pytest.raises(AttributeError):
... test.oops
"""
fetch_attribute = Signal(args=['obj', 'name'])

def __getattr__(self, name):
if name not in self.__dict__:
result = self.fetch_attribute.emit(obj=self, name=name)

if result is not None:
setattr(self, name, result)
else:
raise AttributeError

return getattr(self, name)
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
envlist = py27,py33,pep8,pypy

[testenv]
commands = py.test signalslot/signal.py signalslot/tests.py
commands = py.test signalslot/signal.py signalslot/tests.py signalslot/contrib/dynamic_state.py
deps = -r{toxinidir}/test_requirements.txt

[testenv:pep8]
Expand Down

0 comments on commit a641d24

Please sign in to comment.