Skip to content

Commit

Permalink
feat: trigger a calculated property on mutation
Browse files Browse the repository at this point in the history
* feat: subscribe_mutation supports an event handler as a function
* feat: subscribe_mutation supports an async event handler as a function
  • Loading branch information
FabienArcellier committed Aug 12, 2024
1 parent ffb508d commit 99fbb72
Show file tree
Hide file tree
Showing 4 changed files with 356 additions and 139 deletions.
41 changes: 38 additions & 3 deletions docs/framework/event-handlers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ def hande_click_cleaner(state):
You can subscribe to mutations on a specific key in the state.
This is useful when you want to trigger a function every time a specific key is mutated.

```python
<CodeGroup>
```python simple subscription
import writer as wf

def _increment_counter(state):
Expand All @@ -153,10 +154,44 @@ state.subscribe_mutation('a', _increment_counter)
state['a'] = 2 # trigger _increment_counter mutation
```

```python
state.subscribe_mutation('a.b', _increment_counter) # subscribe to nested key
```python multiple subscriptions
import writer as wf

def _increment_counter(state):
state['my_counter'] += 1

state = wf.init_state({
'title': 'Hello',
'app': {'title', 'Writer Framework'},
'my_counter': 0}
)

state.subscribe_mutation(['title', 'app.title'], _increment_counter) # subscribe to multiple keys

state['title'] = "Hello Pigeon" # trigger _increment_counter mutation
```

```python trigger event handler
import writer as wf

def _increment_counter(state, context: dict, payload: dict, session: dict, ui: WriterUIManager):
if context['event'] == 'mutation' and context['mutation'] == 'a':
if payload['previous_value'] > payload['new_value']:
state['my_counter'] += 1

state = wf.init_state({"a": 1, "my_counter": 0})
state.subscribe_mutation('a', _increment_counter)

state['a'] = 2 # increment my_counter
state['a'] = 3 # increment my_counter
state['a'] = 2 # do nothing
```
</CodeGroup>

<Tip>
`subscribe_mutation` is compatible with event handler signature. It will accept all the arguments
of the event handler (`context`, `payload`, ...).
</Tip>

## Receiving a payload

Expand Down
133 changes: 67 additions & 66 deletions src/writer/app_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from watchdog.observers.polling import PollingObserver

from writer import VERSION
from writer.core import EventHandlerRegistry, MiddlewareRegistry, WriterSession
from writer.core import EventHandlerRegistry, MiddlewareRegistry, WriterSession, use_request_context
from writer.core_ui import ingest_bmc_component_tree
from writer.ss_types import (
AppProcessServerRequest,
Expand Down Expand Up @@ -232,71 +232,72 @@ def _handle_message(self, session_id: str, request: AppProcessServerRequest) ->
"""
import writer

session = None
type = request.type

if type == "sessionInit":
si_req_payload = InitSessionRequestPayload.parse_obj(
request.payload)
return AppProcessServerResponse(
status="ok",
status_message=None,
payload=self._handle_session_init(si_req_payload)
)

session = writer.session_manager.get_session(session_id)
if not session:
raise MessageHandlingException("Session not found.")
session.update_last_active_timestamp()

if type == "checkSession":
return AppProcessServerResponse(
status="ok",
status_message=None,
payload=None
)

if type == "event":
ev_req_payload = WriterEvent.parse_obj(request.payload)
return AppProcessServerResponse(
status="ok",
status_message=None,
payload=self._handle_event(session, ev_req_payload)
)

if type == "stateEnquiry":
return AppProcessServerResponse(
status="ok",
status_message=None,
payload=self._handle_state_enquiry(session)
)

if type == "stateContent":
return AppProcessServerResponse(
status="ok",
status_message=None,
payload=self._handle_state_content(session)
)

if type == "setUserinfo":
session.userinfo = request.payload
return AppProcessServerResponse(
status="ok",
status_message=None,
payload=None
)

if self.mode == "edit" and type == "componentUpdate":
cu_req_payload = ComponentUpdateRequestPayload.parse_obj(
request.payload)
self._handle_component_update(session, cu_req_payload)
return AppProcessServerResponse(
status="ok",
status_message=None,
payload=None
)

raise MessageHandlingException("Invalid event.")
with use_request_context(session_id, request):
session = None
type = request.type

if type == "sessionInit":
si_req_payload = InitSessionRequestPayload.parse_obj(
request.payload)
return AppProcessServerResponse(
status="ok",
status_message=None,
payload=self._handle_session_init(si_req_payload)
)

session = writer.session_manager.get_session(session_id)
if not session:
raise MessageHandlingException("Session not found.")
session.update_last_active_timestamp()

if type == "checkSession":
return AppProcessServerResponse(
status="ok",
status_message=None,
payload=None
)

if type == "event":
ev_req_payload = WriterEvent.parse_obj(request.payload)
return AppProcessServerResponse(
status="ok",
status_message=None,
payload=self._handle_event(session, ev_req_payload)
)

if type == "stateEnquiry":
return AppProcessServerResponse(
status="ok",
status_message=None,
payload=self._handle_state_enquiry(session)
)

if type == "stateContent":
return AppProcessServerResponse(
status="ok",
status_message=None,
payload=self._handle_state_content(session)
)

if type == "setUserinfo":
session.userinfo = request.payload
return AppProcessServerResponse(
status="ok",
status_message=None,
payload=None
)

if self.mode == "edit" and type == "componentUpdate":
cu_req_payload = ComponentUpdateRequestPayload.parse_obj(
request.payload)
self._handle_component_update(session, cu_req_payload)
return AppProcessServerResponse(
status="ok",
status_message=None,
payload=None
)

raise MessageHandlingException("Invalid event.")

def _execute_user_code(self) -> None:
"""
Expand Down
Loading

0 comments on commit 99fbb72

Please sign in to comment.