dara-core 1.16.12__py3-none-any.whl → 1.16.14__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.
- dara/core/persistence.py +100 -19
- dara/core/umd/dara.core.umd.js +11 -1
- {dara_core-1.16.12.dist-info → dara_core-1.16.14.dist-info}/METADATA +10 -10
- {dara_core-1.16.12.dist-info → dara_core-1.16.14.dist-info}/RECORD +7 -7
- {dara_core-1.16.12.dist-info → dara_core-1.16.14.dist-info}/LICENSE +0 -0
- {dara_core-1.16.12.dist-info → dara_core-1.16.14.dist-info}/WHEEL +0 -0
- {dara_core-1.16.12.dist-info → dara_core-1.16.14.dist-info}/entry_points.txt +0 -0
dara/core/persistence.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
|
-
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Set
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Literal, Optional, Set
|
|
5
5
|
from uuid import uuid4
|
|
6
6
|
|
|
7
7
|
import aiorwlock
|
|
@@ -18,16 +18,16 @@ from pydantic import (
|
|
|
18
18
|
from dara.core.auth.definitions import USER
|
|
19
19
|
from dara.core.internal.utils import run_user_handler
|
|
20
20
|
from dara.core.internal.websocket import WS_CHANNEL
|
|
21
|
+
from dara.core.logging import dev_logger
|
|
21
22
|
|
|
22
23
|
if TYPE_CHECKING:
|
|
23
24
|
from dara.core.interactivity.plain_variable import Variable
|
|
25
|
+
from dara.core.internal.websocket import WebsocketManager
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
class PersistenceBackend(BaseModel, abc.ABC):
|
|
27
29
|
"""
|
|
28
|
-
Abstract base class for a BackendStore backend
|
|
29
|
-
|
|
30
|
-
Warning: the API is not stable yet and may change in the future
|
|
30
|
+
Abstract base class for a BackendStore backend.
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
33
|
@abc.abstractmethod
|
|
@@ -60,6 +60,12 @@ class PersistenceBackend(BaseModel, abc.ABC):
|
|
|
60
60
|
Get all the values as a dictionary of key-value pairs
|
|
61
61
|
"""
|
|
62
62
|
|
|
63
|
+
async def subscribe(self, on_value: Callable[[str, Any], Awaitable[None]]):
|
|
64
|
+
"""
|
|
65
|
+
Subscribe to changes in the backend. Called with a callback that should be invoked whenever a value is updated.
|
|
66
|
+
"""
|
|
67
|
+
# Default implementation does nothing, not all backends need to support this
|
|
68
|
+
|
|
63
69
|
|
|
64
70
|
class InMemoryBackend(PersistenceBackend):
|
|
65
71
|
"""
|
|
@@ -173,17 +179,23 @@ class BackendStore(PersistenceStore):
|
|
|
173
179
|
:param backend: the backend to use for storing data; defaults to an in-memory backend
|
|
174
180
|
:param scope: the scope for the store; if 'global' a single value is stored for all users,
|
|
175
181
|
if 'user' a value is stored per user
|
|
182
|
+
:param readonly: whether to use the backend in read-only mode, i.e. skip syncing values from client to backend and raise if write()/delete() is called
|
|
176
183
|
"""
|
|
177
184
|
|
|
178
185
|
uid: str = Field(default_factory=lambda: str(uuid4()))
|
|
179
186
|
backend: PersistenceBackend = Field(default_factory=InMemoryBackend, exclude=True)
|
|
180
187
|
scope: Literal['global', 'user'] = 'global'
|
|
188
|
+
readonly: bool = False
|
|
181
189
|
|
|
182
190
|
default_value: Any = Field(default=None, exclude=True)
|
|
183
191
|
initialized_scopes: Set[str] = Field(default_factory=set, exclude=True)
|
|
184
192
|
|
|
185
193
|
def __init__(
|
|
186
|
-
self,
|
|
194
|
+
self,
|
|
195
|
+
backend: Optional[PersistenceBackend] = None,
|
|
196
|
+
uid: Optional[str] = None,
|
|
197
|
+
scope: Optional[str] = None,
|
|
198
|
+
readonly: bool = False,
|
|
187
199
|
):
|
|
188
200
|
"""
|
|
189
201
|
Persistence store implementation that uses a backend implementation to store data server-side
|
|
@@ -192,6 +204,7 @@ class BackendStore(PersistenceStore):
|
|
|
192
204
|
:param backend: the backend to use for storing data; defaults to an in-memory backend
|
|
193
205
|
:param scope: the scope for the store; if 'global' a single value is stored for all users,
|
|
194
206
|
if 'user' a value is stored per user
|
|
207
|
+
:param readonly: whether to use the backend in read-only mode, i.e. skip syncing values from client to backend and raise if write()/delete() is called
|
|
195
208
|
"""
|
|
196
209
|
kwargs: Dict[str, Any] = {}
|
|
197
210
|
if backend:
|
|
@@ -203,6 +216,9 @@ class BackendStore(PersistenceStore):
|
|
|
203
216
|
if scope:
|
|
204
217
|
kwargs['scope'] = scope
|
|
205
218
|
|
|
219
|
+
if readonly:
|
|
220
|
+
kwargs['readonly'] = readonly
|
|
221
|
+
|
|
206
222
|
super().__init__(**kwargs)
|
|
207
223
|
|
|
208
224
|
async def _get_key(self):
|
|
@@ -235,10 +251,23 @@ class BackendStore(PersistenceStore):
|
|
|
235
251
|
|
|
236
252
|
raise ValueError('User not found when trying to compute the key for a user-scoped store')
|
|
237
253
|
|
|
254
|
+
def _get_user(self, key: str) -> Optional[str]:
|
|
255
|
+
"""
|
|
256
|
+
Get the user for a given key. Returns None if the key is global.
|
|
257
|
+
Reverts the `_get_key` method to get the user for a given key.
|
|
258
|
+
"""
|
|
259
|
+
if key == 'global':
|
|
260
|
+
return None
|
|
261
|
+
|
|
262
|
+
# otherwise key is a user identity_id or identity_name
|
|
263
|
+
return key
|
|
264
|
+
|
|
238
265
|
def _register(self):
|
|
239
266
|
"""
|
|
240
267
|
Register this store in the backend store registry.
|
|
241
|
-
|
|
268
|
+
Warns if the uid is not unique, i.e. another store with the same uid already exists.
|
|
269
|
+
|
|
270
|
+
:return: True if the store was registered, False if it was already registered previously
|
|
242
271
|
"""
|
|
243
272
|
from dara.core.internal.registries import backend_store_registry
|
|
244
273
|
|
|
@@ -250,23 +279,59 @@ class BackendStore(PersistenceStore):
|
|
|
250
279
|
store=self,
|
|
251
280
|
),
|
|
252
281
|
)
|
|
253
|
-
|
|
254
|
-
|
|
282
|
+
return True
|
|
283
|
+
except ValueError:
|
|
284
|
+
dev_logger.info(f'BackendStore with uid "{self.uid}" already exists, reusing the same instance')
|
|
285
|
+
return False
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def ws_mgr(self) -> 'WebsocketManager':
|
|
289
|
+
from dara.core.internal.registries import utils_registry
|
|
290
|
+
|
|
291
|
+
return utils_registry.get('WebsocketManager')
|
|
292
|
+
|
|
293
|
+
def _create_msg(self, value: Any) -> Dict[str, Any]:
|
|
294
|
+
"""
|
|
295
|
+
Create a message to send to the frontend.
|
|
296
|
+
:param value: value to send
|
|
297
|
+
"""
|
|
298
|
+
return {'store_uid': self.uid, 'value': value}
|
|
255
299
|
|
|
256
|
-
async def
|
|
300
|
+
async def _notify_user(self, user_identifier: str, value: Any, ignore_current_channel: bool = True):
|
|
257
301
|
"""
|
|
258
|
-
Notify
|
|
302
|
+
Notify a given user about the new value for this store.
|
|
259
303
|
|
|
304
|
+
:param user_identifier: user to notify
|
|
260
305
|
:param value: value to notify about
|
|
306
|
+
:param ignore_current_channel: if True, ignore the current websocket channel
|
|
261
307
|
"""
|
|
262
|
-
|
|
263
|
-
|
|
308
|
+
return await self.ws_mgr.send_message_to_user(
|
|
309
|
+
user_identifier,
|
|
310
|
+
self._create_msg(value),
|
|
311
|
+
ignore_channel=WS_CHANNEL.get() if ignore_current_channel else None,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
async def _notify_global(self, value: Any, ignore_current_channel: bool = True):
|
|
315
|
+
"""
|
|
316
|
+
Notify all users about the new value for this store.
|
|
317
|
+
|
|
318
|
+
:param value: value to notify about
|
|
319
|
+
:param ignore_current_channel: if True, ignore the current websocket channel
|
|
320
|
+
"""
|
|
321
|
+
return await self.ws_mgr.broadcast(
|
|
322
|
+
self._create_msg(value),
|
|
323
|
+
ignore_channel=WS_CHANNEL.get() if ignore_current_channel else None,
|
|
324
|
+
)
|
|
264
325
|
|
|
265
|
-
|
|
266
|
-
|
|
326
|
+
async def _notify_value(self, value: Any):
|
|
327
|
+
"""
|
|
328
|
+
Notify all clients about the new value for this store.
|
|
329
|
+
Broadcasts to all users if scope is global or sends to the current user if scope is user.
|
|
267
330
|
|
|
331
|
+
:param value: value to notify about
|
|
332
|
+
"""
|
|
268
333
|
if self.scope == 'global':
|
|
269
|
-
return await
|
|
334
|
+
return await self._notify_global(value)
|
|
270
335
|
|
|
271
336
|
# For user scope, we need to find channels for the user and notify them
|
|
272
337
|
user = USER.get()
|
|
@@ -275,7 +340,7 @@ class BackendStore(PersistenceStore):
|
|
|
275
340
|
return
|
|
276
341
|
|
|
277
342
|
user_identifier = user.identity_id or user.identity_name
|
|
278
|
-
return await
|
|
343
|
+
return await self._notify_user(user_identifier, value)
|
|
279
344
|
|
|
280
345
|
async def init(self, variable: 'Variable'):
|
|
281
346
|
"""
|
|
@@ -283,9 +348,19 @@ class BackendStore(PersistenceStore):
|
|
|
283
348
|
|
|
284
349
|
:param variable: the variable to initialize the store for
|
|
285
350
|
"""
|
|
286
|
-
self._register()
|
|
287
351
|
self.default_value = variable.default
|
|
288
352
|
|
|
353
|
+
# only if successfully registered, subscribe to the backend - this makes sure we do it once
|
|
354
|
+
if self._register():
|
|
355
|
+
|
|
356
|
+
async def _on_value(key: str, value: Any):
|
|
357
|
+
# here we explicitly DON'T ignore the current channel, in case we created this variable inside e.g. a py_component we want to notify its creator as well
|
|
358
|
+
if user := self._get_user(key):
|
|
359
|
+
return await self._notify_user(user, value, ignore_current_channel=False)
|
|
360
|
+
return await self._notify_global(value, ignore_current_channel=False)
|
|
361
|
+
|
|
362
|
+
await self.backend.subscribe(_on_value)
|
|
363
|
+
|
|
289
364
|
async def write(self, value: Any, notify=True):
|
|
290
365
|
"""
|
|
291
366
|
Persist a value to the store.
|
|
@@ -296,10 +371,13 @@ class BackendStore(PersistenceStore):
|
|
|
296
371
|
:param value: value to write
|
|
297
372
|
:param notify: whether to broadcast the new value to clients
|
|
298
373
|
"""
|
|
374
|
+
if self.readonly:
|
|
375
|
+
raise ValueError('Cannot write to a read-only store')
|
|
376
|
+
|
|
299
377
|
key = await self._get_key()
|
|
300
378
|
|
|
301
379
|
if notify:
|
|
302
|
-
await self.
|
|
380
|
+
await self._notify_value(value)
|
|
303
381
|
|
|
304
382
|
return await run_user_handler(self.backend.write, (key, value))
|
|
305
383
|
|
|
@@ -325,10 +403,13 @@ class BackendStore(PersistenceStore):
|
|
|
325
403
|
|
|
326
404
|
:param notify: whether to broadcast that the value was deleted to clients
|
|
327
405
|
"""
|
|
406
|
+
if self.readonly:
|
|
407
|
+
raise ValueError('Cannot delete from a read-only store')
|
|
408
|
+
|
|
328
409
|
key = await self._get_key()
|
|
329
410
|
if notify:
|
|
330
411
|
# Schedule notification on delete
|
|
331
|
-
await self.
|
|
412
|
+
await self._notify_value(None)
|
|
332
413
|
return await run_user_handler(self.backend.delete, (key,))
|
|
333
414
|
|
|
334
415
|
async def get_all(self) -> Dict[str, Any]:
|
dara/core/umd/dara.core.umd.js
CHANGED
|
@@ -7527,6 +7527,7 @@ var __privateWrapper = (obj, member, setter, getter) => ({
|
|
|
7527
7527
|
useRetain: Recoil_useRetain,
|
|
7528
7528
|
retentionZone: retentionZone$1
|
|
7529
7529
|
};
|
|
7530
|
+
var Recoil_index_1 = Recoil_index.DefaultValue;
|
|
7530
7531
|
var Recoil_index_5 = Recoil_index.RecoilRoot;
|
|
7531
7532
|
var Recoil_index_8 = Recoil_index.atom;
|
|
7532
7533
|
var Recoil_index_10 = Recoil_index.atomFamily;
|
|
@@ -57943,7 +57944,16 @@ You must set sticky: 'left' | 'right' for the '${bugWithUnderColumnsSticky.Heade
|
|
|
57943
57944
|
return RecoilSync_index_2({
|
|
57944
57945
|
itemKey: variable.store.uid,
|
|
57945
57946
|
refine: Refine_index_6(),
|
|
57946
|
-
storeKey: "BackendStore"
|
|
57947
|
+
storeKey: "BackendStore",
|
|
57948
|
+
write({ write: write2 }, newValue) {
|
|
57949
|
+
if (variable.store.readonly) {
|
|
57950
|
+
return;
|
|
57951
|
+
}
|
|
57952
|
+
if (newValue instanceof Recoil_index_1) {
|
|
57953
|
+
return;
|
|
57954
|
+
}
|
|
57955
|
+
write2(variable.store.uid, newValue);
|
|
57956
|
+
}
|
|
57947
57957
|
});
|
|
57948
57958
|
}
|
|
57949
57959
|
function getSessionKey(uid2) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dara-core
|
|
3
|
-
Version: 1.16.
|
|
3
|
+
Version: 1.16.14
|
|
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.16.
|
|
23
|
+
Requires-Dist: create-dara-app (==1.16.14)
|
|
24
24
|
Requires-Dist: croniter (>=1.0.15,<3.0.0)
|
|
25
25
|
Requires-Dist: cryptography (>=42.0.4)
|
|
26
|
-
Requires-Dist: dara-components (==1.16.
|
|
26
|
+
Requires-Dist: dara-components (==1.16.14) ; extra == "all"
|
|
27
27
|
Requires-Dist: exceptiongroup (>=1.1.3,<2.0.0)
|
|
28
28
|
Requires-Dist: fastapi (>=0.115.0,<0.116.0)
|
|
29
29
|
Requires-Dist: fastapi_vite_dara (==0.4.0)
|
|
@@ -53,7 +53,7 @@ Description-Content-Type: text/markdown
|
|
|
53
53
|
|
|
54
54
|
# Dara Application Framework
|
|
55
55
|
|
|
56
|
-
<img src="https://github.com/causalens/dara/blob/v1.16.
|
|
56
|
+
<img src="https://github.com/causalens/dara/blob/v1.16.14/img/dara_light.svg?raw=true">
|
|
57
57
|
|
|
58
58
|

|
|
59
59
|
[](https://www.apache.org/licenses/LICENSE-2.0)
|
|
@@ -98,7 +98,7 @@ source .venv/bin/activate
|
|
|
98
98
|
dara start
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-

|
|
102
102
|
|
|
103
103
|
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:
|
|
104
104
|
|
|
@@ -115,9 +115,9 @@ Explore some of our favorite apps - a great way of getting started and getting t
|
|
|
115
115
|
|
|
116
116
|
| Dara App | Description |
|
|
117
117
|
| -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
118
|
-
|  | Demonstrates how to use incorporate a LLM chat box into your decision app to understand model insights |
|
|
119
|
+
|  | 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 |
|
|
120
|
+
|  | 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. |
|
|
121
121
|
|
|
122
122
|
Check out our [App Gallery](https://dara.causalens.com/gallery) for more inspiration!
|
|
123
123
|
|
|
@@ -144,9 +144,9 @@ And the supporting UI packages and tools.
|
|
|
144
144
|
- `ui-utils` - miscellaneous utility functions
|
|
145
145
|
- `ui-widgets` - widget components
|
|
146
146
|
|
|
147
|
-
More information on the repository structure can be found in the [CONTRIBUTING.md](https://github.com/causalens/dara/blob/v1.16.
|
|
147
|
+
More information on the repository structure can be found in the [CONTRIBUTING.md](https://github.com/causalens/dara/blob/v1.16.14/CONTRIBUTING.md) file.
|
|
148
148
|
|
|
149
149
|
## License
|
|
150
150
|
|
|
151
|
-
Dara is open-source and licensed under the [Apache 2.0 License](https://github.com/causalens/dara/blob/v1.16.
|
|
151
|
+
Dara is open-source and licensed under the [Apache 2.0 License](https://github.com/causalens/dara/blob/v1.16.14/LICENSE).
|
|
152
152
|
|
|
@@ -80,8 +80,8 @@ dara/core/metrics/__init__.py,sha256=2UqpWHv-Ie58QLJIHJ9Szfjq8xifAuwy5FYGUIFwWtI
|
|
|
80
80
|
dara/core/metrics/cache.py,sha256=bGXwjO_rSc-FkS3PnPi1mvIZf1x-hvmG113dAUk1g-Y,2616
|
|
81
81
|
dara/core/metrics/runtime.py,sha256=YP-6Dz0GeI9_Yr7bUk_-OqShyFySGH_AKpDO126l6es,1833
|
|
82
82
|
dara/core/metrics/utils.py,sha256=aKaa_hskV3M3h4xOGZYvegDLq_OWOEUlslkQKrrPQiI,2281
|
|
83
|
-
dara/core/persistence.py,sha256=
|
|
84
|
-
dara/core/umd/dara.core.umd.js,sha256=
|
|
83
|
+
dara/core/persistence.py,sha256=lEyrOfcTDQ2CvlvZZVAuz9fBGwyzjRa3yMw-BBWpOoM,14032
|
|
84
|
+
dara/core/umd/dara.core.umd.js,sha256=zv9FZRX2NtIjgKhiqjzyeFmwuGnkRtxgxFNVfzePAOI,4870020
|
|
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=4km21GLMLM2cr42SwehLUD7DJOlzvhwHl5hdG1OqmIM,2274
|
|
@@ -104,8 +104,8 @@ dara/core/visual/themes/__init__.py,sha256=aM4mgoIYo2neBSw5FRzswsht7PUKjLthiHLmF
|
|
|
104
104
|
dara/core/visual/themes/dark.py,sha256=UQGDooOc8ric73eHs9E0ltYP4UCrwqQ3QxqN_fb4PwY,1942
|
|
105
105
|
dara/core/visual/themes/definitions.py,sha256=nS_gQvOzCt5hTmj74d0_siq_9QWuj6wNuir4VCHy0Dk,2779
|
|
106
106
|
dara/core/visual/themes/light.py,sha256=-Tviq8oEwGbdFULoDOqPuHO0UpAZGsBy8qFi0kAGolQ,1944
|
|
107
|
-
dara_core-1.16.
|
|
108
|
-
dara_core-1.16.
|
|
109
|
-
dara_core-1.16.
|
|
110
|
-
dara_core-1.16.
|
|
111
|
-
dara_core-1.16.
|
|
107
|
+
dara_core-1.16.14.dist-info/LICENSE,sha256=r9u1w2RvpLMV6YjuXHIKXRBKzia3fx_roPwboGcLqCc,10944
|
|
108
|
+
dara_core-1.16.14.dist-info/METADATA,sha256=FZ4vUFZrIFf0k_WCWoLd-m4P8T7BpBVI91dtu-zZMbA,7473
|
|
109
|
+
dara_core-1.16.14.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
110
|
+
dara_core-1.16.14.dist-info/entry_points.txt,sha256=H__D5sNIGuPIhVam0DChNL-To5k8Y7nY7TAFz9Mz6cc,139
|
|
111
|
+
dara_core-1.16.14.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|