-
Notifications
You must be signed in to change notification settings - Fork 26
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
Rationals for the commit "Huge blob commit to drive reactor forward" #34
Comments
Hey @jbjuin, nice to have you here! The answer is "it's complicated" I had parked reactor for a year or so, not giving it too much attention and focused on experimenting with HTMX, look at djhtmx. It is kind of the same but with HTMX. I started djhtmx from reactor, is basically a fork using HTMX, and had been using it for more than a year in production (that's why this both projects don't have tests, because the tests are tests of my production code, if I break something on this projects I will notice immediately). During this year I noticed that:
Other changes were:- I had to separate the initialization state from the state of the component, because I noticed they are not the same, and learned this from ReactJS and MithrilJS.
Do you have any ideas, opinions, questions? Just let me know... Have a great start of the year! |
Hi @edelvalle thanks for all those explanations. I didn't try djhtmx although I should since it looks great as well. I use reactor in some tools but on an old version: the one before the commit changing the syntax for the event handlers (when you removed the prefix I don't really know what to do about that. I built a bunch of tooling around it and I may continue with it and make a fork from the version I use. I never had any problems with serialization (nor JS transpilation overhead being a problem) or the other needs that you mention: cache and sending the instance in the broadcast. This being said I do have questions about this version:
The thing I want to do is test reactor under some parametrized load (number of sessions basically):
I've planned some time in the coming weeks to clean my code and publish it so that you can have a look. Anyway it's a joy to develop with reactor. You did a great work on that ! |
I'm so sorry to hear this, but... I think I took some decisions were not the best and this are amendments.
Seriously? I would had never done such drastic changes if I knew this library was actually being used by other people other than me. I assumed unicorn is way more popular, well documented, tested and so on... this library release it here as a byproduct of my actual work. Regarding your questions:
Before calling # Cache: the render of the component can be cached if you define a cache key
_cache_key: str = None
# expiration time of the cache
_cache_time = 300
# if True will refresh the cache on each render
_cache_touch = True
In my mental model is easier, I just state what I want to be subscribed to without doing it in an imperative way.
Before I the broadcast would be something like Now the change comes the channel name you subscribed to class Action:
# Model action
UPDATED = "UPDATED"
DELETED = "DELETED"
CREATED = "CREATED"
# M2M actions
ADDED = "ADDED"
REMOVED = "REMOVED"
CLEARED = "CLEARED" If you have a TODO list and you have a component per item in the list you can do: class ItemComponent(Component):
item = Model[Item]
@property
def subscriptions(self):
return f"item.{self.item.id}"
def mutation(self, channel, instance, action):
self.item = instance
if action == Action.DELETED:
self.destroy() If you are just rendering the @property
def _cache_key(self):
return self.item.name
We are evaluating to use this, why? because we could send cross-components signal to update components that do not have a direct relation.
Had not done that, would be very interesting!
Than you very much and sorry for the disturbance I caused... 😸 |
Ha ha don't worry ! We are still evaluating reactor for a coming project and we may switch to this new version. Maybe we could integrate some of the modifications we did. I will have to read this new version more deeply.
Well Unicorn looks good as well but with more magic (my feeling). I also need "backend push" features that seems to lack in Unicorn . The code of reactor is small, simple and even though it's not fully tested it is small enough to be easily understood and fixed if needed. Plus, I know it quite well now :)
I see. This may be useful indeed with some "flat" data where you can easily know when something changed or not, so that you can derive the key from that "something".
OK so you introduced a "mutation" handler. That is interesting as well. I ended up using a different approach with my "store" companion app, re-using the classical events handlers (
Ok so this is the caching mechanism ! Great... this is how I understood it: the key is the name so you do not hit the DB except if the name changes. Cool !
Yeah this was one of my need as well (hence the reactor "store" companion app I ended writing). So with this mechanism in place we don't need it anymore ! Great !
Yes. I'm still not sure how to do that properly since I will have to use some headless browser client. This is not a small 1 hour task.
Again it was not against your choices, this work is truly amazing: simple and efficient. Love it !
If it fits our needs I will
When this is done I will probably propose a pull request to go back to the Any other idea to coordinate efforts on that ? Thanks again for your explanations and I hope you will keep working on that ! Edit: just saw that the README is updated with those new mechanism ! Sorry for the questions, last time I checked it was not updated... |
I try to keep the levels of magic to the minimum, just magic to make things comfortable but no more.
I will probably add a mechanism to send events cross components, and will look something like a pubsub pattern. Where a component subscribes to a channel and other publishes events with arguments and stuff and the first components receives the whole thing and decides what to do then.
I'm just lazy
What!? Do you have a jinja2 companion app? I'm looking forward to look into that and make the template system configurable and flexible, so you can tell the whole reactor or a single component to use Jinja. Nice!
Would you mind having something shorter like To do this this things would have to be modified:
I can do it for you if you tell me what prefix do you like... Regarding stress testing and so on, do you wanna have a call about that? Check my email on the commits and send me an email and we can talk any time. I'm in Berlin, Germany (CET zone) |
Hi @edelvalle, I just started trying reactor 3 and here is a first remark about the new subscription system. In reactor 2 we had the
In reactor 3,
My questions & remarks are:
This generic broadcasting system would not be tied to django models and would answer your need of a "pub/sub" pattern. The "model actions broadcasting" system could use it to send the instance & action. Does it makes sense ? What do you think ? I continue my exploration of the new features and will report my remarks here. About you previous questions:
Cheers ! |
Hi, I wanted to add this generic broadcasting and have a way for the component to receive those message. I will make a PR and you check my proposal, and I will ask you several stuff in that PR so we design in code and not talking on the vacuum. Then I will do the changes for the Have a great time and thanks a lot! |
Hi @edelvalle Do you prefer to integrate it in reactor 3 or make it available through a companion app ? |
Nice, let's do it as part of the app. |
Hey @edelvalle, I have a question about the integration in reactor 3 of one of my companion app I wrote (for reactor 2): "reactor store". Here is a usage example: # this is a mutation
def set_key(state, new_value):
state["my-key"] = new_value
return state
class MyStore(ReactorStore):
# the shared state
STATE = {"my_key": "A value"}
# all mutations
MUTATIONS = {"set_key": set_key}
class MyComponent(Component, MyStore):
def mount(self, **kwargs):
# initialize the store
self.store_init()
self.value = self.store_get("my_key")
self.store_watch("my_key")
# this handler will be called when "my_key" is modified by the mutation "set_key".
def receive_set_key(self, my_key=None):
print("my_key was changed by the set_key mutation") Here are the few methods defined:
Practically the state is a simple dict that is attached to the I'm now wondering how I could do that same pattern in reactor 3 since there is no _root_component in components anymore. Would it be possible, on component creation to add a reference to the Cheers ! PS: Just thinking out loud: maybe an invisible "store" component included in the page could store the state and then mutations are just wrappers around the pub/sub pattern. Seems that it may possible this way... |
One of the reasons I removed the root component is that there was a reference cycle between all components and the root component, which could cause memory leaks due the the reference counting. This is something I avoided in this new design. That's why you see that the components do not have direct access to the repository and the Doing the invisible store component was my first idea, since it fits in the model and isolates the state like in an actor model. But if you need to read the state to take a decision you will end up with two functions. One to request state and other to analyze it. That could be complicated and ugly. I have no clear answer right now. If you come up with an idea make a proposal. |
Hi @edelvalle, just to keep you informed that I have a draft working for the reactor 3 "store" pattern. So it basically expose a "get" and a "subscribe" API to components. It's a simple shared in-memory KV store. Components can get value from it, subscribe to key changes and change values through fixed mutations. The "commit" and "subscribe" use the notifications + broadcast system that you added. Subscriptions to store keys is done by decorating the As you mentioned the tricky point was the "get" operation. Since I want it to be "synchronous", ie One possible solution is: def store_get(Store, keys):
"""Get values from the store Store keys."""
# the hack: it works... but feels hacky :)
import gc
for obj in gc.get_objects():
if isinstance(obj, Store):
# here we return at the first found instance of the Store: those store components must be singles !
return [obj.store_state[key] for key in keys]
raise Exception("No key found") In my implementation multiple Store can be used but a single component of each store can be instantiated at the same time. Instead of relying on the gc I could have used some weakref on instances by overloading init or the classmethod new but I couldn't get it to work. Well, all this being said, this is a way to do it. The API of this store proposal is not optimal, there is a lot of edge cases and no error handling for now. What I can do is make a gist with the code + example usage so that you can try it ? Cheers PS: Maybe a more formal way of building Reactor App could be done, like Vue is doing it, with an "app" component on which we could add plugins (stores, a router, etc) that would be injected on all components instances. |
I will think about this, but I want to have a clear use case to implement something like this. |
Hi @edelvalle, glad to see that you came back to this project !
Do you have any rationals about your complete rewrite of reactor ?
I remember that you wanted to avoid external dependencies and pydantic is a huge one.
Is this for speed reason ?
Cheers,
The text was updated successfully, but these errors were encountered: