dara-core 1.12.4__py3-none-any.whl → 1.12.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,7 +14,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  """
17
-
17
+ import asyncio
18
+ import inspect
18
19
  import math
19
20
  import uuid
20
21
  from contextvars import ContextVar
@@ -51,9 +52,22 @@ class DaraClientMessage(BaseModel):
51
52
 
52
53
 
53
54
  class CustomClientMessagePayload(BaseModel):
55
+ rchan: Optional[str] = Field(default=None, alias='__rchan')
56
+ """Return channel if the message is expected to have a response for"""
57
+
54
58
  kind: str
55
59
  data: Any
56
60
 
61
+ def dict(self, *args, **kwargs):
62
+ # Force by_alias to True to use __rchan name
63
+ result = super().dict(*args, **{**kwargs, 'by_alias': True})
64
+
65
+ # remove rchan if None
66
+ if '__rchan' in result and result.get('__rchan') is None:
67
+ result.pop('__rchan')
68
+
69
+ return result
70
+
57
71
 
58
72
  class CustomClientMessage(BaseModel):
59
73
  """
@@ -71,6 +85,9 @@ class ServerMessagePayload(BaseModel):
71
85
  rchan: Optional[str] = Field(default=None, alias='__rchan')
72
86
  """Return channel if the message is expected to have a response for"""
73
87
 
88
+ response_for: Optional[str] = Field(default=None, alias='__response_for')
89
+ """ID of the __rchan included in the original client message if this message is a response to a client message"""
90
+
74
91
  class Config:
75
92
  extra = 'allow'
76
93
 
@@ -82,6 +99,10 @@ class ServerMessagePayload(BaseModel):
82
99
  if '__rchan' in result and result.get('__rchan') is None:
83
100
  result.pop('__rchan')
84
101
 
102
+ # remove response_for if None
103
+ if '__response_for' in result and result.get('__response_for') is None:
104
+ result.pop('__response_for')
105
+
85
106
  return result
86
107
 
87
108
 
@@ -159,11 +180,13 @@ class WebSocketHandler:
159
180
  """
160
181
  await self.send_stream.send(message)
161
182
 
162
- async def process_client_message(self, message: ClientMessage):
183
+ def process_client_message(self, message: ClientMessage):
163
184
  """
164
185
  Process a message received from the client.
165
186
  Handles resolving pending responses.
166
187
 
188
+ Can return a coroutine to be awaited by the caller.
189
+
167
190
  :param message: The message to process
168
191
  """
169
192
  if message.type == 'message':
@@ -190,26 +213,51 @@ class WebSocketHandler:
190
213
  self.pending_responses[message_id] = (event, message.message)
191
214
  event.set()
192
215
 
216
+ return None
217
+
193
218
  if message.type == 'custom':
194
219
  # import required internals
195
220
  from dara.core.internal.registries import custom_ws_handlers_registry
196
- from dara.core.internal.utils import run_user_handler
197
221
 
198
222
  data = message.message.data
199
223
  kind = message.message.kind
200
224
 
201
225
  try:
202
226
  handler = custom_ws_handlers_registry.get(kind)
203
- response = await run_user_handler(handler, args=(self.channel_id, data))
204
227
 
205
- # If the handler returns a response, send it to the client
206
- if response is not None:
207
- await self.send_message(
208
- CustomServerMessage(message=CustomServerMessagePayload(kind=kind, data=response))
209
- )
228
+ # Sync handler are processed directly, async ones scheduled as a task
229
+ if inspect.iscoroutinefunction(handler):
230
+
231
+ async def wrapper():
232
+ response = await handler(self.channel_id, data)
233
+ if response is not None:
234
+ await self.send_message(
235
+ CustomServerMessage(
236
+ message=CustomServerMessagePayload(
237
+ kind=kind, data=response, __response_for=message.message.rchan
238
+ )
239
+ )
240
+ )
241
+
242
+ asyncio.create_task(wrapper())
243
+ return None
244
+ else:
245
+ response = handler(self.channel_id, data)
246
+ if response is not None:
247
+ # Return a coroutine for the caller to await
248
+ return self.send_message(
249
+ CustomServerMessage(
250
+ message=CustomServerMessagePayload(
251
+ kind=kind, data=response, __response_for=message.message.rchan
252
+ )
253
+ )
254
+ )
210
255
  except KeyError as e:
211
256
  eng_logger.error(f'No handler found for custom message kind {kind}', e)
212
- return
257
+ return None
258
+
259
+ # unreachable but needed for pylint to be happy
260
+ return None
213
261
 
214
262
  async def send_and_wait(self, message: ServerMessage) -> Optional[Any]:
215
263
  """
@@ -446,8 +494,10 @@ async def ws_handler(websocket: WebSocket, token: Optional[str] = Query(default=
446
494
  else:
447
495
  try:
448
496
  parsed_data = parse_obj_as(ClientMessage, data)
449
- # Process the message in a separate task group to avoid blocking the receive task
450
- tg.start_soon(handler.process_client_message, parsed_data)
497
+ result = handler.process_client_message(parsed_data)
498
+ # Process the resulting coroutine before moving on to next message
499
+ if inspect.iscoroutine(result):
500
+ await result
451
501
  except Exception as e:
452
502
  eng_logger.error('Error processing client WS message', error=e)
453
503
 
@@ -26409,6 +26409,19 @@ var __privateWrapper = (obj, member, setter, getter) => ({
26409
26409
  }
26410
26410
  }).call(commonjsGlobal$1);
26411
26411
  })(lodash$2, lodash$2.exports);
26412
+ let nanoid = (size2 = 21) => crypto.getRandomValues(new Uint8Array(size2)).reduce((id, byte) => {
26413
+ byte &= 63;
26414
+ if (byte < 36) {
26415
+ id += byte.toString(36);
26416
+ } else if (byte < 62) {
26417
+ id += (byte - 26).toString(36).toUpperCase();
26418
+ } else if (byte > 62) {
26419
+ id += "-";
26420
+ } else {
26421
+ id += "_";
26422
+ }
26423
+ return id;
26424
+ }, "");
26412
26425
  styled__default.default.span`
26413
26426
  display: flex;
26414
26427
  flex-direction: column;
@@ -29610,7 +29623,8 @@ var __privateWrapper = (obj, member, setter, getter) => ({
29610
29623
  CodeComponentThemes2["LIGHT"] = "light";
29611
29624
  })(CodeComponentThemes || (CodeComponentThemes = {}));
29612
29625
  styled__default.default.pre`
29613
- min-width: fit-content;
29626
+ overflow-x: auto;
29627
+
29614
29628
  margin: 0;
29615
29629
  padding: 1rem;
29616
29630
 
@@ -56561,8 +56575,31 @@ You must set sticky: 'left' | 'right' for the '${bugWithUnderColumnsSticky.Heade
56561
56575
  );
56562
56576
  }
56563
56577
  }
56564
- sendCustomMessage(kind, data) {
56578
+ sendCustomMessage(kind, data, awaitResponse = false) {
56565
56579
  if (this.socket.readyState === WebSocket.OPEN) {
56580
+ if (awaitResponse) {
56581
+ const rchan = nanoid();
56582
+ return new Promise((resolve) => {
56583
+ const subscription = this.customMessages$().pipe().subscribe({
56584
+ next: (msg) => {
56585
+ if (msg.message.__response_for === rchan) {
56586
+ resolve(msg);
56587
+ subscription.unsubscribe();
56588
+ }
56589
+ }
56590
+ });
56591
+ this.socket.send(
56592
+ JSON.stringify({
56593
+ message: {
56594
+ data,
56595
+ kind,
56596
+ __rchan: rchan
56597
+ },
56598
+ type: "custom"
56599
+ })
56600
+ );
56601
+ });
56602
+ }
56566
56603
  this.socket.send(
56567
56604
  JSON.stringify({
56568
56605
  message: {
@@ -56572,6 +56609,7 @@ You must set sticky: 'left' | 'right' for the '${bugWithUnderColumnsSticky.Heade
56572
56609
  type: "custom"
56573
56610
  })
56574
56611
  );
56612
+ return Promise.resolve(null);
56575
56613
  }
56576
56614
  }
56577
56615
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dara-core
3
- Version: 1.12.4
3
+ Version: 1.12.5
4
4
  Summary: Dara Framework Core
5
5
  Home-page: https://dara.causalens.com/
6
6
  License: Apache-2.0
@@ -20,10 +20,10 @@ Requires-Dist: async-asgi-testclient (>=1.4.11,<2.0.0)
20
20
  Requires-Dist: certifi (>=2024.7.4)
21
21
  Requires-Dist: click (==8.1.3)
22
22
  Requires-Dist: colorama (>=0.4.6,<0.5.0)
23
- Requires-Dist: create-dara-app (==1.12.4)
23
+ Requires-Dist: create-dara-app (==1.12.5)
24
24
  Requires-Dist: croniter (>=1.0.15,<2.0.0)
25
25
  Requires-Dist: cryptography (>=42.0.4)
26
- Requires-Dist: dara-components (==1.12.4) ; extra == "all"
26
+ Requires-Dist: dara-components (==1.12.5) ; extra == "all"
27
27
  Requires-Dist: exceptiongroup (>=1.1.3,<2.0.0)
28
28
  Requires-Dist: fastapi (==0.109.0)
29
29
  Requires-Dist: fastapi-vite (==0.3.1)
@@ -51,7 +51,7 @@ Description-Content-Type: text/markdown
51
51
 
52
52
  # Dara Application Framework
53
53
 
54
- <img src="https://github.com/causalens/dara/blob/v1.12.4/img/dara_light.svg?raw=true">
54
+ <img src="https://github.com/causalens/dara/blob/v1.12.5/img/dara_light.svg?raw=true">
55
55
 
56
56
  ![Master tests](https://github.com/causalens/dara/actions/workflows/tests.yml/badge.svg?branch=master)
57
57
  [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
@@ -96,7 +96,7 @@ source .venv/bin/activate
96
96
  dara start
97
97
  ```
98
98
 
99
- ![Dara App](https://github.com/causalens/dara/blob/v1.12.4/img/components_gallery.png?raw=true)
99
+ ![Dara App](https://github.com/causalens/dara/blob/v1.12.5/img/components_gallery.png?raw=true)
100
100
 
101
101
  Note: `pip` installation uses [PEP 660](https://peps.python.org/pep-0660/) `pyproject.toml`-based editable installs which require `pip >= 21.3` and `setuptools >= 64.0.0`. You can upgrade both with:
102
102
 
@@ -113,9 +113,9 @@ Explore some of our favorite apps - a great way of getting started and getting t
113
113
 
114
114
  | Dara App | Description |
115
115
  | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
116
- | ![Large Language Model](https://github.com/causalens/dara/blob/v1.12.4/img/llm.png?raw=true) | Demonstrates how to use incorporate a LLM chat box into your decision app to understand model insights |
117
- | ![Plot Interactivity](https://github.com/causalens/dara/blob/v1.12.4/img/plot_interactivity.png?raw=true) | Demonstrates how to enable the user to interact with plots, trigger actions based on clicks, mouse movements and other interactions with `Bokeh` or `Plotly` plots |
118
- | ![Graph Editor](https://github.com/causalens/dara/blob/v1.12.4/img/graph_viewer.png?raw=true) | Demonstrates how to use the `CausalGraphViewer` component to display your graphs or networks, customising the displayed information through colors and tooltips, and updating the page based on user interaction. |
116
+ | ![Large Language Model](https://github.com/causalens/dara/blob/v1.12.5/img/llm.png?raw=true) | Demonstrates how to use incorporate a LLM chat box into your decision app to understand model insights |
117
+ | ![Plot Interactivity](https://github.com/causalens/dara/blob/v1.12.5/img/plot_interactivity.png?raw=true) | Demonstrates how to enable the user to interact with plots, trigger actions based on clicks, mouse movements and other interactions with `Bokeh` or `Plotly` plots |
118
+ | ![Graph Editor](https://github.com/causalens/dara/blob/v1.12.5/img/graph_viewer.png?raw=true) | Demonstrates how to use the `CausalGraphViewer` component to display your graphs or networks, customising the displayed information through colors and tooltips, and updating the page based on user interaction. |
119
119
 
120
120
  Check out our [App Gallery](https://dara.causalens.com/gallery) for more inspiration!
121
121
 
@@ -127,9 +127,9 @@ This repository covers the Dara Application Framework first-party packages.
127
127
  - `dara-components`: Components for the Dara Framework.
128
128
  - `create-dara-app`: A CLI tool for creating new Dara applications.
129
129
 
130
- More information on the repository structure can be found in the [CONTRIBUTING.md](https://github.com/causalens/dara/blob/v1.12.4/CONTRIBUTING.md) file.
130
+ More information on the repository structure can be found in the [CONTRIBUTING.md](https://github.com/causalens/dara/blob/v1.12.5/CONTRIBUTING.md) file.
131
131
 
132
132
  ## License
133
133
 
134
- Dara is open-source and licensed under the [Apache 2.0 License](https://github.com/causalens/dara/blob/v1.12.4/LICENSE).
134
+ Dara is open-source and licensed under the [Apache 2.0 License](https://github.com/causalens/dara/blob/v1.12.5/LICENSE).
135
135
 
@@ -60,7 +60,7 @@ dara/core/internal/settings.py,sha256=wAWxl-HXjq7PW3twe_CrR-UuMRw9VBudC3eRmevZAh
60
60
  dara/core/internal/store.py,sha256=qVyU7JfC3zE2vYC2mfjmvECWMlFS9b-nMF1k-alg4Y8,7756
61
61
  dara/core/internal/tasks.py,sha256=XK-GTIyge8RBYAfzNs3rmLYVNSKIarCzPdqRSVGg-4M,24728
62
62
  dara/core/internal/utils.py,sha256=b1YYkn8qHl6-GY6cCm2MS1NXRS9j_rElYCKMZOxJgrY,8232
63
- dara/core/internal/websocket.py,sha256=CCmjbnMpIqtb2suNCJaI4p8WJokjK4WpyMKbEEJ8IJo,18164
63
+ dara/core/internal/websocket.py,sha256=dhvE7KnolBHwPy9E6Ac1tjzOn0fAtdhDdcMUs47WuuQ,20138
64
64
  dara/core/jinja/index.html,sha256=iykqiRh3H_HkcjHJeeSRXRu45nZ2y1sZX5FLdPRhlQY,726
65
65
  dara/core/jinja/index_autojs.html,sha256=MRF5J0vNfzZQm9kPEeLl23sbr08fVSRd_PAUD6Fkc_0,1253
66
66
  dara/core/js_tooling/custom_js_scaffold/index.tsx,sha256=FEzSV5o5Nyzxw6eXvGLi7BkEBkXf3brV34_7ATLnY7o,68
@@ -81,7 +81,7 @@ dara/core/metrics/cache.py,sha256=ybofUhZO0TCHeyhB_AtldWk1QTmTKh7GucTXpOkeTFA,25
81
81
  dara/core/metrics/runtime.py,sha256=YP-6Dz0GeI9_Yr7bUk_-OqShyFySGH_AKpDO126l6es,1833
82
82
  dara/core/metrics/utils.py,sha256=rYlBinxFc7VehFT5cTNXLk8gC74UEj7ZGq6vLgIDpSg,2247
83
83
  dara/core/persistence.py,sha256=TO94rPAN7jxZKVCC5YA4eE7GGDoNlCPe-BkkItV2VUE,10379
84
- dara/core/umd/dara.core.umd.js,sha256=2AENw8gbOqzcrJb4t6wRkhib7sSHN4E4EpUxbbsPQ0g,4875712
84
+ dara/core/umd/dara.core.umd.js,sha256=eAzeW0wT8sdeoYKEmTdPPlD_OsxRP9PHJUTko912TAM,4876798
85
85
  dara/core/umd/style.css,sha256=YQtQ4veiSktnyONl0CU1iU1kKfcQhreH4iASi1MP7Ak,4095007
86
86
  dara/core/visual/__init__.py,sha256=QN0wbG9HPQ_vXh8BO8DnBXeYLIENVTNtRmYzZf1lx7c,577
87
87
  dara/core/visual/components/__init__.py,sha256=O-Em_glGdZNO0LLl2RWmJSrQiXKxliXg_PuhVXGT81I,1811
@@ -105,8 +105,8 @@ dara/core/visual/themes/__init__.py,sha256=aM4mgoIYo2neBSw5FRzswsht7PUKjLthiHLmF
105
105
  dara/core/visual/themes/dark.py,sha256=UQGDooOc8ric73eHs9E0ltYP4UCrwqQ3QxqN_fb4PwY,1942
106
106
  dara/core/visual/themes/definitions.py,sha256=m3oN0txs65MZepqjj7AKMMxybf2aq5fTjcTwJmHqEbk,2744
107
107
  dara/core/visual/themes/light.py,sha256=-Tviq8oEwGbdFULoDOqPuHO0UpAZGsBy8qFi0kAGolQ,1944
108
- dara_core-1.12.4.dist-info/LICENSE,sha256=r9u1w2RvpLMV6YjuXHIKXRBKzia3fx_roPwboGcLqCc,10944
109
- dara_core-1.12.4.dist-info/METADATA,sha256=kQSyg7YDJh31Z6uHrVx9mOnmE7sObbF7858Iu4SLnns,6771
110
- dara_core-1.12.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
111
- dara_core-1.12.4.dist-info/entry_points.txt,sha256=H__D5sNIGuPIhVam0DChNL-To5k8Y7nY7TAFz9Mz6cc,139
112
- dara_core-1.12.4.dist-info/RECORD,,
108
+ dara_core-1.12.5.dist-info/LICENSE,sha256=r9u1w2RvpLMV6YjuXHIKXRBKzia3fx_roPwboGcLqCc,10944
109
+ dara_core-1.12.5.dist-info/METADATA,sha256=tVMv9uBzRnUMr8zkJch56lrN0WLyJF5KZqzffynj174,6771
110
+ dara_core-1.12.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
111
+ dara_core-1.12.5.dist-info/entry_points.txt,sha256=H__D5sNIGuPIhVam0DChNL-To5k8Y7nY7TAFz9Mz6cc,139
112
+ dara_core-1.12.5.dist-info/RECORD,,