dara-core 1.14.0a2__py3-none-any.whl → 1.14.0a3__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/auth/routes.py +5 -20
- dara/core/auth/utils.py +120 -1
- dara/core/internal/websocket.py +6 -40
- dara/core/umd/dara.core.umd.js +41 -43
- {dara_core-1.14.0a2.dist-info → dara_core-1.14.0a3.dist-info}/METADATA +10 -10
- {dara_core-1.14.0a2.dist-info → dara_core-1.14.0a3.dist-info}/RECORD +9 -9
- {dara_core-1.14.0a2.dist-info → dara_core-1.14.0a3.dist-info}/LICENSE +0 -0
- {dara_core-1.14.0a2.dist-info → dara_core-1.14.0a3.dist-info}/WHEEL +0 -0
- {dara_core-1.14.0a2.dist-info → dara_core-1.14.0a3.dist-info}/entry_points.txt +0 -0
dara/core/auth/routes.py
CHANGED
|
@@ -40,7 +40,7 @@ from dara.core.auth.definitions import (
|
|
|
40
40
|
AuthError,
|
|
41
41
|
SessionRequestBody,
|
|
42
42
|
)
|
|
43
|
-
from dara.core.auth.utils import decode_token
|
|
43
|
+
from dara.core.auth.utils import cached_refresh_token, decode_token
|
|
44
44
|
from dara.core.logging import dev_logger
|
|
45
45
|
|
|
46
46
|
auth_router = APIRouter()
|
|
@@ -139,33 +139,18 @@ async def handle_refresh_token(
|
|
|
139
139
|
),
|
|
140
140
|
)
|
|
141
141
|
|
|
142
|
-
from dara.core.internal.registries import
|
|
143
|
-
auth_registry,
|
|
144
|
-
utils_registry,
|
|
145
|
-
websocket_registry,
|
|
146
|
-
)
|
|
147
|
-
from dara.core.internal.websocket import WebsocketManager
|
|
142
|
+
from dara.core.internal.registries import auth_registry
|
|
148
143
|
|
|
149
144
|
auth_config: BaseAuthConfig = auth_registry.get('auth_config')
|
|
150
|
-
ws_manager: WebsocketManager = utils_registry.get('WebsocketManager')
|
|
151
145
|
|
|
152
146
|
try:
|
|
153
147
|
# decode the old token ignoring expiry date
|
|
154
148
|
old_token_data = decode_token(credentials.credentials, options={'verify_exp': False})
|
|
155
149
|
|
|
156
150
|
# Refresh logic up to implementation - passing in old token data so session_id can be preserved
|
|
157
|
-
session_token, refresh_token =
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
# so they can update the data in ContextVars
|
|
161
|
-
async def notify_ws_connections():
|
|
162
|
-
session_token_data = decode_token(session_token)
|
|
163
|
-
channels = websocket_registry.get(old_token_data.session_id)
|
|
164
|
-
for channel in channels:
|
|
165
|
-
if handler := ws_manager.handlers.get(channel):
|
|
166
|
-
await handler.update_token(session_token_data)
|
|
167
|
-
|
|
168
|
-
background_tasks.add_task(notify_ws_connections)
|
|
151
|
+
session_token, refresh_token = await cached_refresh_token(
|
|
152
|
+
auth_config.refresh_token, old_token_data, dara_refresh_token
|
|
153
|
+
)
|
|
169
154
|
|
|
170
155
|
# Using 'Strict' as it is only used for the refresh-token endpoint so cross-site requests are not expected
|
|
171
156
|
response.set_cookie(
|
dara/core/auth/utils.py
CHANGED
|
@@ -15,11 +15,13 @@ See the License for the specific language governing permissions and
|
|
|
15
15
|
limitations under the License.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
+
import asyncio
|
|
18
19
|
import uuid
|
|
19
20
|
from datetime import datetime, timedelta, timezone
|
|
20
|
-
from typing import List, Optional, Union
|
|
21
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
21
22
|
|
|
22
23
|
import jwt
|
|
24
|
+
from anyio import to_thread
|
|
23
25
|
|
|
24
26
|
from dara.core.auth.definitions import (
|
|
25
27
|
EXPIRED_TOKEN_ERROR,
|
|
@@ -100,3 +102,120 @@ def get_user_data():
|
|
|
100
102
|
)
|
|
101
103
|
|
|
102
104
|
return user_data
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class AsyncTokenRefreshCache:
|
|
108
|
+
"""
|
|
109
|
+
An asynchronous cache for token refresh operations that handles concurrent requests
|
|
110
|
+
and provides time-based cache invalidation.
|
|
111
|
+
|
|
112
|
+
This cache is designed to prevent multiple simultaneous refresh attempts with the
|
|
113
|
+
same refresh token, while also providing a short-term cache to reduce unnecessary
|
|
114
|
+
token refreshes from multiple tabs/windows.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def __init__(self, ttl_seconds: int = 5):
|
|
118
|
+
self.cache: Dict[str, Tuple[Any, datetime]] = {}
|
|
119
|
+
self.locks: Dict[str, asyncio.Lock] = {}
|
|
120
|
+
self.locks_lock = asyncio.Lock()
|
|
121
|
+
self.ttl = timedelta(seconds=ttl_seconds)
|
|
122
|
+
|
|
123
|
+
async def _get_or_create_lock(self, key: str) -> asyncio.Lock:
|
|
124
|
+
"""
|
|
125
|
+
Get an existing lock for the given key or create a new one if it doesn't exist.
|
|
126
|
+
|
|
127
|
+
This method is thread-safe and ensures that only one lock exists per key.
|
|
128
|
+
|
|
129
|
+
:param key: The key to get or create a lock for.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
async with self.locks_lock:
|
|
133
|
+
if key not in self.locks:
|
|
134
|
+
self.locks[key] = asyncio.Lock()
|
|
135
|
+
return self.locks[key]
|
|
136
|
+
|
|
137
|
+
def _cleanup_old_entries(self):
|
|
138
|
+
"""
|
|
139
|
+
Remove expired entries from both the cache and locks dictionaries.
|
|
140
|
+
|
|
141
|
+
This method is called before each cache access to prevent memory leaks
|
|
142
|
+
from accumulated expired entries.
|
|
143
|
+
"""
|
|
144
|
+
current_time = datetime.now()
|
|
145
|
+
expired_keys = [key for key, (_, timestamp) in self.cache.items() if current_time - timestamp > self.ttl]
|
|
146
|
+
for key in expired_keys:
|
|
147
|
+
self.cache.pop(key, None)
|
|
148
|
+
# We can modify self.locks here because we're always under an async lock when calling this
|
|
149
|
+
self.locks.pop(key, None)
|
|
150
|
+
|
|
151
|
+
def get_cached_value(self, key: str) -> Tuple[Any, bool]:
|
|
152
|
+
"""
|
|
153
|
+
Retrieve a value from the cache if it exists and hasn't expired.
|
|
154
|
+
|
|
155
|
+
:param key: The key to retrieve from the cache.
|
|
156
|
+
:return: A tuple containing the value and a boolean indicating whether the value was found.
|
|
157
|
+
"""
|
|
158
|
+
self._cleanup_old_entries()
|
|
159
|
+
if key in self.cache:
|
|
160
|
+
value, timestamp = self.cache[key]
|
|
161
|
+
if datetime.now() - timestamp <= self.ttl:
|
|
162
|
+
return value, True
|
|
163
|
+
return None, False
|
|
164
|
+
|
|
165
|
+
def set_cached_value(self, key: str, value: Any):
|
|
166
|
+
"""
|
|
167
|
+
Set a value in the cache with the current timestamp.
|
|
168
|
+
|
|
169
|
+
:param key: The key to set in the cache.
|
|
170
|
+
:param value: The value to set in the cache.
|
|
171
|
+
"""
|
|
172
|
+
self.cache[key] = (value, datetime.now())
|
|
173
|
+
|
|
174
|
+
def clear(self):
|
|
175
|
+
"""
|
|
176
|
+
Clear the cache and locks dictionaries.
|
|
177
|
+
"""
|
|
178
|
+
self.cache.clear()
|
|
179
|
+
self.locks.clear()
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
token_refresh_cache = AsyncTokenRefreshCache(ttl_seconds=5)
|
|
183
|
+
"""
|
|
184
|
+
Shared token refresh cache instance
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
async def cached_refresh_token(
|
|
189
|
+
func: Callable[[TokenData, str], Tuple[str, str]], old_token_data: TokenData, refresh_token: str
|
|
190
|
+
):
|
|
191
|
+
"""
|
|
192
|
+
A utility to run a token refresh method with caching to prevent multiple concurrent refreshes
|
|
193
|
+
and short-term caching to reduce unnecessary refreshes from multiple tabs/windows.
|
|
194
|
+
|
|
195
|
+
:param func: The function to run to refresh the token
|
|
196
|
+
:param old_token_data: The old token data
|
|
197
|
+
:param refresh_token: The refresh token to use
|
|
198
|
+
"""
|
|
199
|
+
cache_key = refresh_token
|
|
200
|
+
|
|
201
|
+
# check for cache hit
|
|
202
|
+
cached_result, found = token_refresh_cache.get_cached_value(cache_key)
|
|
203
|
+
if found:
|
|
204
|
+
return cached_result
|
|
205
|
+
|
|
206
|
+
# cache miss, acquire lock so only one call for given refresh_token is allowed
|
|
207
|
+
lock = await token_refresh_cache._get_or_create_lock(cache_key)
|
|
208
|
+
|
|
209
|
+
async with lock:
|
|
210
|
+
# check cache again in case another call already refreshed the token while we were waiting
|
|
211
|
+
cached_result, found = token_refresh_cache.get_cached_value(cache_key)
|
|
212
|
+
if found:
|
|
213
|
+
return cached_result
|
|
214
|
+
|
|
215
|
+
# Run the refresh function
|
|
216
|
+
result = await to_thread.run_sync(func, old_token_data, refresh_token)
|
|
217
|
+
|
|
218
|
+
# update cache
|
|
219
|
+
token_refresh_cache.set_cached_value(cache_key, result)
|
|
220
|
+
|
|
221
|
+
return result
|
dara/core/internal/websocket.py
CHANGED
|
@@ -156,16 +156,6 @@ class WebSocketHandler:
|
|
|
156
156
|
Stream containing messages to send to the client.
|
|
157
157
|
"""
|
|
158
158
|
|
|
159
|
-
token_send_stream: MemoryObjectSendStream[TokenData]
|
|
160
|
-
"""
|
|
161
|
-
Stream for sending token updates to the WS connection.
|
|
162
|
-
"""
|
|
163
|
-
|
|
164
|
-
token_receive_stream: MemoryObjectReceiveStream[TokenData]
|
|
165
|
-
"""
|
|
166
|
-
Stream for receiving token updates in the WS connection.
|
|
167
|
-
"""
|
|
168
|
-
|
|
169
159
|
pending_responses: Dict[str, Tuple[Event, Optional[Any]]]
|
|
170
160
|
"""
|
|
171
161
|
A map of pending responses from the client. The key is the message ID and the value is a tuple of the event to
|
|
@@ -180,35 +170,9 @@ class WebSocketHandler:
|
|
|
180
170
|
self.receive_stream = receive_stream
|
|
181
171
|
self.send_stream = send_stream
|
|
182
172
|
|
|
183
|
-
token_send_stream, token_receive_stream = anyio.create_memory_object_stream[TokenData](math.inf)
|
|
184
|
-
self.token_send_stream = token_send_stream
|
|
185
|
-
self.token_receive_stream = token_receive_stream
|
|
186
|
-
|
|
187
173
|
self.channel_id = channel_id
|
|
188
174
|
self.pending_responses = {}
|
|
189
175
|
|
|
190
|
-
async def update_token(self, token_data: TokenData):
|
|
191
|
-
"""
|
|
192
|
-
Update the token for the client.
|
|
193
|
-
Should be used if the token is refreshed or changed in some way
|
|
194
|
-
so the live WS connection can update it's ContextVars accordingly
|
|
195
|
-
and they're up to date in custom message handlers.
|
|
196
|
-
|
|
197
|
-
:param token_data: The new token data
|
|
198
|
-
"""
|
|
199
|
-
await self.token_send_stream.send(token_data)
|
|
200
|
-
|
|
201
|
-
def get_token_update(self) -> Optional[TokenData]:
|
|
202
|
-
"""
|
|
203
|
-
Get the latest token update for the client.
|
|
204
|
-
|
|
205
|
-
:return: The latest token update
|
|
206
|
-
"""
|
|
207
|
-
try:
|
|
208
|
-
return self.token_receive_stream.receive_nowait()
|
|
209
|
-
except Exception:
|
|
210
|
-
return None
|
|
211
|
-
|
|
212
176
|
async def send_message(self, message: ServerMessage):
|
|
213
177
|
"""
|
|
214
178
|
Send a message to the client.
|
|
@@ -528,13 +492,15 @@ async def ws_handler(websocket: WebSocket, token: Optional[str] = Query(default=
|
|
|
528
492
|
# as the latter does not properly handle disconnections e.g. when relaoading the server
|
|
529
493
|
data = await websocket.receive_json()
|
|
530
494
|
|
|
531
|
-
# update Auth context vars for the WS connection
|
|
532
|
-
while new_token_data := handler.get_token_update():
|
|
533
|
-
update_context(new_token_data)
|
|
534
|
-
|
|
535
495
|
# Heartbeat to keep connection alive
|
|
536
496
|
if data['type'] == 'ping':
|
|
537
497
|
await websocket.send_json({'type': 'pong', 'message': None})
|
|
498
|
+
if data['type'] == 'token_update':
|
|
499
|
+
try:
|
|
500
|
+
# update Auth context vars for the WS connection
|
|
501
|
+
update_context(decode_token(data['message']))
|
|
502
|
+
except Exception as e:
|
|
503
|
+
eng_logger.error('Error updating token data', error=e)
|
|
538
504
|
else:
|
|
539
505
|
try:
|
|
540
506
|
parsed_data = parse_obj_as(ClientMessage, data)
|
dara/core/umd/dara.core.umd.js
CHANGED
|
@@ -30,14 +30,10 @@ var __privateWrapper = (obj, member, setter, getter) => ({
|
|
|
30
30
|
return __privateGet(obj, member, getter);
|
|
31
31
|
}
|
|
32
32
|
});
|
|
33
|
-
var __privateMethod = (obj, member, method) => {
|
|
34
|
-
__accessCheck(obj, member, "access private method");
|
|
35
|
-
return method;
|
|
36
|
-
};
|
|
37
33
|
(function(global2, factory) {
|
|
38
34
|
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("react"), require("@tanstack/react-query"), require("react-dom"), require("styled-components")) : typeof define === "function" && define.amd ? define(["exports", "react", "@tanstack/react-query", "react-dom", "styled-components"], factory) : (global2 = typeof globalThis !== "undefined" ? globalThis : global2 || self, factory((global2.dara = global2.dara || {}, global2.dara.core = {}), global2.React, global2.ReactQuery, global2.ReactDOM, global2.styled));
|
|
39
35
|
})(this, function(exports, React, reactQuery, ReactDOM, styled) {
|
|
40
|
-
var
|
|
36
|
+
var _locks, _subscribers, _pingInterval, _socketUrl, _reconnectCount, _events$, _parentBus, _instance, _observers;
|
|
41
37
|
"use strict";
|
|
42
38
|
const _interopDefaultLegacy = (e3) => e3 && typeof e3 === "object" && "default" in e3 ? e3 : { default: e3 };
|
|
43
39
|
function _interopNamespace(e3) {
|
|
@@ -5095,7 +5091,7 @@ var __privateMethod = (obj, member, method) => {
|
|
|
5095
5091
|
subscriptions.current.delete(key);
|
|
5096
5092
|
}
|
|
5097
5093
|
}, [subscriptions]);
|
|
5098
|
-
const updateState = useCallback$1$1((
|
|
5094
|
+
const updateState = useCallback$1$1((_state, key) => {
|
|
5099
5095
|
if (subscriptions.current.has(key)) {
|
|
5100
5096
|
forceUpdate([]);
|
|
5101
5097
|
}
|
|
@@ -5265,7 +5261,7 @@ var __privateMethod = (obj, member, method) => {
|
|
|
5265
5261
|
return prevState.loadable.is(nextState.loadable) && prevState.key === nextState.key ? prevState : nextState;
|
|
5266
5262
|
}, [getState2]);
|
|
5267
5263
|
useEffect$3$1(() => {
|
|
5268
|
-
const subscription = subscribeToRecoilValue$1(storeRef.current, recoilValue, (
|
|
5264
|
+
const subscription = subscribeToRecoilValue$1(storeRef.current, recoilValue, (_state) => {
|
|
5269
5265
|
setState(updateState);
|
|
5270
5266
|
}, componentName);
|
|
5271
5267
|
setState(updateState);
|
|
@@ -5293,7 +5289,7 @@ var __privateMethod = (obj, member, method) => {
|
|
|
5293
5289
|
useEffect$3$1(() => {
|
|
5294
5290
|
const store2 = storeRef.current;
|
|
5295
5291
|
const storeState = store2.getState();
|
|
5296
|
-
const subscription = subscribeToRecoilValue$1(store2, recoilValue, (
|
|
5292
|
+
const subscription = subscribeToRecoilValue$1(store2, recoilValue, (_state) => {
|
|
5297
5293
|
var _prevLoadableRef$curr;
|
|
5298
5294
|
if (!Recoil_gkx("recoil_suppress_rerender_in_callback")) {
|
|
5299
5295
|
return forceUpdate([]);
|
|
@@ -49400,7 +49396,7 @@ You must set sticky: 'left' | 'right' for the '${bugWithUnderColumnsSticky.Heade
|
|
|
49400
49396
|
key: "render",
|
|
49401
49397
|
value: function render2() {
|
|
49402
49398
|
var _props = this.props, children = _props.children, className = _props.className, disableHeight = _props.disableHeight, disableWidth = _props.disableWidth, style = _props.style;
|
|
49403
|
-
var
|
|
49399
|
+
var _state = this.state, height = _state.height, width = _state.width;
|
|
49404
49400
|
var outerStyle = { overflow: "visible" };
|
|
49405
49401
|
var childParams = {};
|
|
49406
49402
|
var bailoutOnChildren = false;
|
|
@@ -54958,16 +54954,12 @@ You must set sticky: 'left' | 'right' for the '${bugWithUnderColumnsSticky.Heade
|
|
|
54958
54954
|
var cloneDeep_1 = cloneDeep;
|
|
54959
54955
|
class GlobalStore {
|
|
54960
54956
|
constructor() {
|
|
54961
|
-
__privateAdd(this, _notify);
|
|
54962
|
-
__privateAdd(this, _state, void 0);
|
|
54963
54957
|
__privateAdd(this, _locks, void 0);
|
|
54964
54958
|
__privateAdd(this, _subscribers, void 0);
|
|
54965
|
-
__privateSet(this, _state, {});
|
|
54966
54959
|
__privateSet(this, _locks, {});
|
|
54967
54960
|
__privateSet(this, _subscribers, {});
|
|
54968
54961
|
}
|
|
54969
54962
|
clear() {
|
|
54970
|
-
__privateSet(this, _state, {});
|
|
54971
54963
|
__privateSet(this, _locks, {});
|
|
54972
54964
|
__privateSet(this, _subscribers, {});
|
|
54973
54965
|
}
|
|
@@ -54975,14 +54967,17 @@ You must set sticky: 'left' | 'right' for the '${bugWithUnderColumnsSticky.Heade
|
|
|
54975
54967
|
if (__privateGet(this, _locks)[key]) {
|
|
54976
54968
|
return __privateGet(this, _locks)[key];
|
|
54977
54969
|
}
|
|
54978
|
-
return
|
|
54970
|
+
return localStorage.getItem(key);
|
|
54979
54971
|
}
|
|
54980
54972
|
getValueSync(key) {
|
|
54981
|
-
return
|
|
54973
|
+
return localStorage.getItem(key);
|
|
54982
54974
|
}
|
|
54983
54975
|
setValue(key, value) {
|
|
54984
|
-
|
|
54985
|
-
|
|
54976
|
+
if (value === null) {
|
|
54977
|
+
localStorage.removeItem(key);
|
|
54978
|
+
} else {
|
|
54979
|
+
localStorage.setItem(key, value);
|
|
54980
|
+
}
|
|
54986
54981
|
}
|
|
54987
54982
|
async replaceValue(key, fn) {
|
|
54988
54983
|
if (__privateGet(this, _locks)[key]) {
|
|
@@ -55011,21 +55006,19 @@ You must set sticky: 'left' | 'right' for the '${bugWithUnderColumnsSticky.Heade
|
|
|
55011
55006
|
if (!__privateGet(this, _subscribers)[key]) {
|
|
55012
55007
|
__privateGet(this, _subscribers)[key] = [];
|
|
55013
55008
|
}
|
|
55014
|
-
|
|
55009
|
+
const subFunc = (e3) => {
|
|
55010
|
+
if (e3.storageArea === localStorage && e3.key === key) {
|
|
55011
|
+
callback(e3.newValue);
|
|
55012
|
+
}
|
|
55013
|
+
};
|
|
55014
|
+
window.addEventListener("storage", subFunc);
|
|
55015
55015
|
return () => {
|
|
55016
|
-
|
|
55016
|
+
window.removeEventListener("storage", subFunc);
|
|
55017
55017
|
};
|
|
55018
55018
|
}
|
|
55019
55019
|
}
|
|
55020
|
-
_state = new WeakMap();
|
|
55021
55020
|
_locks = new WeakMap();
|
|
55022
55021
|
_subscribers = new WeakMap();
|
|
55023
|
-
_notify = new WeakSet();
|
|
55024
|
-
notify_fn = function(key, value) {
|
|
55025
|
-
if (__privateGet(this, _subscribers)[key]) {
|
|
55026
|
-
__privateGet(this, _subscribers)[key].forEach((cb) => cb(value));
|
|
55027
|
-
}
|
|
55028
|
-
};
|
|
55029
55022
|
const store = new GlobalStore();
|
|
55030
55023
|
class RequestExtrasSerializable {
|
|
55031
55024
|
constructor(extras) {
|
|
@@ -55056,7 +55049,7 @@ You must set sticky: 'left' | 'right' for the '${bugWithUnderColumnsSticky.Heade
|
|
|
55056
55049
|
}
|
|
55057
55050
|
async function request(url, options, extras) {
|
|
55058
55051
|
var _a, _b;
|
|
55059
|
-
const sessionToken = await store.getValue(
|
|
55052
|
+
const sessionToken = await store.getValue(getTokenKey());
|
|
55060
55053
|
const mergedOptions = extras ? { ...options, ...extras } : options;
|
|
55061
55054
|
const { headers, ...other } = mergedOptions;
|
|
55062
55055
|
const headersInterface = new Headers(headers);
|
|
@@ -55077,7 +55070,7 @@ You must set sticky: 'left' | 'right' for the '${bugWithUnderColumnsSticky.Heade
|
|
|
55077
55070
|
});
|
|
55078
55071
|
if (response.status === 401 || response.status === 403) {
|
|
55079
55072
|
try {
|
|
55080
|
-
const refreshedToken = await store.replaceValue(
|
|
55073
|
+
const refreshedToken = await store.replaceValue(getTokenKey(), async () => {
|
|
55081
55074
|
const refreshResponse = await fetch(`${baseUrl}/api/auth/refresh-token`, {
|
|
55082
55075
|
headers: headersInterface,
|
|
55083
55076
|
...other,
|
|
@@ -56573,6 +56566,16 @@ You must set sticky: 'left' | 'right' for the '${bugWithUnderColumnsSticky.Heade
|
|
|
56573
56566
|
);
|
|
56574
56567
|
}
|
|
56575
56568
|
}
|
|
56569
|
+
updateToken(newToken) {
|
|
56570
|
+
if (this.socket.readyState === WebSocket.OPEN) {
|
|
56571
|
+
this.socket.send(
|
|
56572
|
+
JSON.stringify({
|
|
56573
|
+
message: newToken,
|
|
56574
|
+
type: "token_update"
|
|
56575
|
+
})
|
|
56576
|
+
);
|
|
56577
|
+
}
|
|
56578
|
+
}
|
|
56576
56579
|
sendCustomMessage(kind, data, awaitResponse = false) {
|
|
56577
56580
|
if (this.socket.readyState === WebSocket.OPEN) {
|
|
56578
56581
|
if (awaitResponse) {
|
|
@@ -58093,13 +58096,13 @@ You must set sticky: 'left' | 'right' for the '${bugWithUnderColumnsSticky.Heade
|
|
|
58093
58096
|
return resolver(getOrRegisterPlainVariable(variable, client, taskContext, extras));
|
|
58094
58097
|
}
|
|
58095
58098
|
function tokenSubscribe(cb) {
|
|
58096
|
-
return store.subscribe(
|
|
58099
|
+
return store.subscribe(getTokenKey(), cb);
|
|
58097
58100
|
}
|
|
58098
58101
|
function getSessionToken() {
|
|
58099
|
-
return store.getValueSync(
|
|
58102
|
+
return store.getValueSync(getTokenKey());
|
|
58100
58103
|
}
|
|
58101
58104
|
function setSessionToken(token) {
|
|
58102
|
-
store.setValue(
|
|
58105
|
+
store.setValue(getTokenKey(), token);
|
|
58103
58106
|
}
|
|
58104
58107
|
function useSessionToken() {
|
|
58105
58108
|
return React__namespace.useSyncExternalStore(tokenSubscribe, getSessionToken);
|
|
@@ -59122,18 +59125,8 @@ Inferred class string: "${iconClasses}."`
|
|
|
59122
59125
|
const isMounted = React.useRef(false);
|
|
59123
59126
|
if (!isMounted.current) {
|
|
59124
59127
|
isMounted.current = true;
|
|
59125
|
-
|
|
59128
|
+
setSessionToken(getToken());
|
|
59126
59129
|
}
|
|
59127
|
-
React.useEffect(() => {
|
|
59128
|
-
return store.subscribe("sessionToken", (newToken) => {
|
|
59129
|
-
const key = getTokenKey();
|
|
59130
|
-
if (newToken) {
|
|
59131
|
-
localStorage.setItem(key, newToken);
|
|
59132
|
-
} else {
|
|
59133
|
-
localStorage.removeItem(key);
|
|
59134
|
-
}
|
|
59135
|
-
});
|
|
59136
|
-
}, []);
|
|
59137
59130
|
if (isLoading) {
|
|
59138
59131
|
return /* @__PURE__ */ React__default.default.createElement(Center, null, /* @__PURE__ */ React__default.default.createElement(DefaultFallback, null));
|
|
59139
59132
|
}
|
|
@@ -60398,6 +60391,7 @@ Inferred class string: "${iconClasses}."`
|
|
|
60398
60391
|
function TemplateRoot() {
|
|
60399
60392
|
var _a, _b, _c;
|
|
60400
60393
|
const token = useSessionToken();
|
|
60394
|
+
const [previousToken, setPreviousToken] = React.useState(token);
|
|
60401
60395
|
const { data: config2 } = useConfig();
|
|
60402
60396
|
const { data: template, isLoading: templateLoading } = useTemplate(config2 == null ? void 0 : config2.template);
|
|
60403
60397
|
const { data: actions, isLoading: actionsLoading } = useActions();
|
|
@@ -60405,7 +60399,11 @@ Inferred class string: "${iconClasses}."`
|
|
|
60405
60399
|
const [wsClient, setWsClient] = React.useState();
|
|
60406
60400
|
React.useEffect(() => {
|
|
60407
60401
|
cleanSessionCache(token);
|
|
60408
|
-
|
|
60402
|
+
if (token !== previousToken) {
|
|
60403
|
+
wsClient.updateToken(token);
|
|
60404
|
+
setPreviousToken(token);
|
|
60405
|
+
}
|
|
60406
|
+
}, [token, previousToken]);
|
|
60409
60407
|
React.useEffect(() => {
|
|
60410
60408
|
if (config2 == null ? void 0 : config2.title) {
|
|
60411
60409
|
document.title = config2.title;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dara-core
|
|
3
|
-
Version: 1.14.
|
|
3
|
+
Version: 1.14.0a3
|
|
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.14.0-alpha.
|
|
23
|
+
Requires-Dist: create-dara-app (==1.14.0-alpha.3)
|
|
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.14.0-alpha.
|
|
26
|
+
Requires-Dist: dara-components (==1.14.0-alpha.3) ; 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.14.0-alpha.
|
|
54
|
+
<img src="https://github.com/causalens/dara/blob/v1.14.0-alpha.3/img/dara_light.svg?raw=true">
|
|
55
55
|
|
|
56
56
|

|
|
57
57
|
[](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
|
-

|
|
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
|
-
|  | Demonstrates how to use incorporate a LLM chat box into your decision app to understand model insights |
|
|
117
|
+
|  | 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
|
+
|  | 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
|
|
|
@@ -142,9 +142,9 @@ And the supporting UI packages and tools.
|
|
|
142
142
|
- `ui-utils` - miscellaneous utility functions
|
|
143
143
|
- `ui-widgets` - widget components
|
|
144
144
|
|
|
145
|
-
More information on the repository structure can be found in the [CONTRIBUTING.md](https://github.com/causalens/dara/blob/v1.14.0-alpha.
|
|
145
|
+
More information on the repository structure can be found in the [CONTRIBUTING.md](https://github.com/causalens/dara/blob/v1.14.0-alpha.3/CONTRIBUTING.md) file.
|
|
146
146
|
|
|
147
147
|
## License
|
|
148
148
|
|
|
149
|
-
Dara is open-source and licensed under the [Apache 2.0 License](https://github.com/causalens/dara/blob/v1.14.0-alpha.
|
|
149
|
+
Dara is open-source and licensed under the [Apache 2.0 License](https://github.com/causalens/dara/blob/v1.14.0-alpha.3/LICENSE).
|
|
150
150
|
|
|
@@ -4,8 +4,8 @@ dara/core/auth/__init__.py,sha256=H0bJoXff5wIRZmHvvQ3y9p5SXA9lM8OuLCGceYGqfb0,85
|
|
|
4
4
|
dara/core/auth/base.py,sha256=jZNuCMoBHQcxWeLpTUzcxdbkbWUJ42jbtKgnnrwvNVA,3201
|
|
5
5
|
dara/core/auth/basic.py,sha256=IMkoC1OeeRmnmjIqPHpybs8zSdbLlNKYLRvj08ajirg,4692
|
|
6
6
|
dara/core/auth/definitions.py,sha256=fx-VCsElP9X97gM0Eql-4lFpLa0UryokmGZhQQat2NU,3511
|
|
7
|
-
dara/core/auth/routes.py,sha256=
|
|
8
|
-
dara/core/auth/utils.py,sha256=
|
|
7
|
+
dara/core/auth/routes.py,sha256=k5y9G-mBDNM3sYX3rk1p1khZPKBD97rPdrs8PV2LSJs,7269
|
|
8
|
+
dara/core/auth/utils.py,sha256=KRLKTd3bGxChGREBEzPVarmwxIwb8SB0JazBtUTDTc4,7277
|
|
9
9
|
dara/core/base_definitions.py,sha256=r_W_qk6_VvvskbPEPjTF6xUh3o_lkNBWMFhN1Pic8Ks,14868
|
|
10
10
|
dara/core/cli.py,sha256=ycTB7QHCB-74OnKnjXqkXq-GBqyjBqo7u4v1kTgv2jE,7656
|
|
11
11
|
dara/core/configuration.py,sha256=8VynDds7a_uKXSpeNvjOUK3qfclg0WPniFEspL-6fi8,21153
|
|
@@ -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=
|
|
63
|
+
dara/core/internal/websocket.py,sha256=Im4a5iqdsf8aqjkimleKOX99wE-kvHlHDyICw7bgcCk,20640
|
|
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=
|
|
84
|
+
dara/core/umd/dara.core.umd.js,sha256=NS7UoNAICvBgpZmZlKQzv9a2x5IhQO8vA_Jfq2HO73E,4877537
|
|
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.14.
|
|
109
|
-
dara_core-1.14.
|
|
110
|
-
dara_core-1.14.
|
|
111
|
-
dara_core-1.14.
|
|
112
|
-
dara_core-1.14.
|
|
108
|
+
dara_core-1.14.0a3.dist-info/LICENSE,sha256=r9u1w2RvpLMV6YjuXHIKXRBKzia3fx_roPwboGcLqCc,10944
|
|
109
|
+
dara_core-1.14.0a3.dist-info/METADATA,sha256=InpFqVDrz4d-lYG2gFDs175nDCJB3923gfqcEiLRyc4,7457
|
|
110
|
+
dara_core-1.14.0a3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
111
|
+
dara_core-1.14.0a3.dist-info/entry_points.txt,sha256=H__D5sNIGuPIhVam0DChNL-To5k8Y7nY7TAFz9Mz6cc,139
|
|
112
|
+
dara_core-1.14.0a3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|