-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
events #31
Comments
@henri-hulski mentions the signals API in Cerebral (JS) is interesting. |
A nice description, how the signals in Cerebral works is in the README of redux-action-tree, which is a port of Cerebral signals to Redux. It starts with a short description of its concept:
For sure for Morepath this can only be an inspiration, as we've to solve slightly different issues. |
The Cerebral signals implementation was extracted to action-tree, which is written in typescript, to allow other project to use it as well.
|
A short indroduction from http://www.cerebraljs.com/signals which could also be relevant to Morepath:
|
While with dispatch a single function ends up getting called, with events there is the implication of one to many behavior: to a single event you could have multiple subscribers. There's also the implication of inheritance. If B gets added and B subclasses from A, a subscriber that listens for add events of A sshould also get notified. If something gets added to folder D and it subclasses from C, a subscriber that listens for add events to folder C should also get notified. So it would appear @taschini I'm curious whether you have ideas on this. |
seems like can be implemented by simply using a register method on PredicateRegistry that does not check for duplicates. following monkeypatch added events in form of .publish() and subscribe() method to Dispatch import reg
from reg.dispatch import Dispatch, validate_signature
from reg.context import DispatchMethod
from reg.predicate import PredicateRegistry
from functools import partial
def _dispatch_subscribe(self, func=None, **key_dict):
if func is None:
return partial(self.subscribe, **key_dict)
validate_signature(func, self.wrapped_func)
predicate_key = self.registry.key_dict_to_predicate_key(key_dict)
self.registry.subscribe(predicate_key, func)
return func
def _dispatch_publish(self, *args, **kwargs):
subscribers = self.by_args(*args, **kwargs).all_matches
return list([sub(*args, **kwargs) for sub in subscribers])
def _dispatchmethod_publish(self, app, *args, **kwargs):
subscribers = self.by_args(*args, **kwargs).all_matches
return list([sub(app, *args, **kwargs) for sub in subscribers])
def _registry_subscribe(self, key, value):
for index, key_item in zip(self.indexes, key):
index.setdefault(key_item, set()).add(value)
if not getattr(PredicateRegistry, '__pubsub_patched', False):
PredicateRegistry.subscribe = _registry_subscribe
PredicateRegistry.__pubsub_patched = True
if not getattr(Dispatch, '__pubsub_patched', False):
Dispatch.subscribe = _dispatch_subscribe
Dispatch.publish = _dispatch_publish
Dispatch.__pubsub_patched = True
if not getattr(DispatchMethod, '__pubsub_dispatchmethod_patched', False):
DispatchMethod.publish = _dispatchmethod_publish
DispatchMethod.__pubsub_dispatchmethod_patched = True and tests import reg
def test_event():
class Model(object):
pass
class SubModel(Model):
pass
@reg.dispatch(reg.match_instance('model'),
reg.match_key('signal', lambda model, signal: signal))
def event(model, signal):
raise NotImplementedError
@event.subscribe(model=Model, signal='event')
def one(model, signal):
return 1
@event.subscribe(model=Model, signal='event')
def two(model, signal):
return 2
@event.subscribe(model=SubModel, signal='event')
def three(model, signal):
return 3
mobj = Model()
smobj = SubModel()
assert list(sorted(event.publish(model=mobj, signal='event'))) == [1, 2]
assert list(
sorted(event.publish(model=smobj, signal='event'))) == [1, 2, 3]
def test_event_dispatchmethod():
class App(object):
@reg.dispatch_method(reg.match_instance('model'),
reg.match_key('signal', lambda self, model, signal: signal))
def event(self, model, signal):
raise NotImplementedError
class Model(object):
pass
class SubModel(Model):
pass
@App.event.subscribe(model=Model, signal='event')
def one(app, model, signal):
return 1
@App.event.subscribe(model=Model, signal='event')
def two(app, model, signal):
return 2
@App.event.subscribe(model=SubModel, signal='event')
def three(app, model, signal):
return 3
app = App()
mobj = Model()
smobj = SubModel()
assert list(sorted(
app.event.publish(app, model=mobj, signal='event'))) == [1, 2]
assert list(sorted(
app.event.publish(app, model=smobj, signal='event'))) == [1, 2, 3] |
and if we can have #21 fixed, then there's no need to have .publish() as currently the dispatch function only executes the first function that match . Also, not quite sure on how to make app always passed to the function during publish when implemented as dispatch method |
Two comments on the use of the 'signal' predicate:
If we remove the restriction from the registry so that we can register multiple components for the same key, I think it becomes unpredictable which component ends up being called in the normal function call scenario. So I'm -1 on that for our plain dispatch function. I don't think we have a use case where we want a dispatch function to sometimes behave like a normal function and sometimes to call all the things we've registered on it. Those are separate use cases. So I think it would make sense to define a new kind of dispatch function that dispatches to all matches instead of to only the first one. Its API is the same as for dispatch functions: we could use .register to register subscribers and you call it to call all matches. We could layer this over the dispatch function we already have, and we register an object that we can register components with and when called calls all those components.
We also need to figure out what the default implementation does in this case. When should it get called? You've made it raise NotImplementedError but I think that's wrong. I suspect we should want it to be called always, even if no other matches exist, as the last match. Enabling this would require quite a bit of refactoring to avoid code duplication, especially to also have a dispatch_all_method. Incidentally we have an API "all_matches" on the LookupEntry that already returns all matches. (and matches that returns an iterator). |
On signal predicate, that is just an example implementation, so its only in the tests :). And yeah, using the function itself to distinguish which signal is which would be more consistent with the rest of reg API. Currently i'm using match_key for signal in my project to avoid defining dectate directive and dispatch method for each and every signal type i want to dispatch (which is quite a bit). on having separate reg dispatch function, +1 on that idea, that will help cleanly separate normal dispatch and subscriber dispatch. on default implementation, executing the default implementation as final function make sense to me. |
any idea if this is going to be implemented? .. i'm heavily using this now |
Any update on this? |
I think we could go two ways:
If the latter is possible, then I'd prefer that. We might need to expose more APIs in Reg to do so. |
Hey, thanks for your answer. Thx |
Some way to build events on top of Reg. We should be able to select all things that match a certain set of predicates, including things that match less strongly. Then fire off events at it.
The text was updated successfully, but these errors were encountered: