dara-core 1.14.0a0__py3-none-any.whl → 1.14.0a2__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/base.py CHANGED
@@ -91,10 +91,13 @@ class BaseAuthConfig(BaseModel, abc.ABC):
91
91
  :param token: encoded token
92
92
  """
93
93
 
94
- def refresh_token(self, refresh_token: str) -> tuple[str, str]:
94
+ def refresh_token(self, old_token: TokenData, refresh_token: str) -> tuple[str, str]:
95
95
  """
96
96
  Create a new session token and refresh token from a refresh token.
97
97
 
98
+ Note: the new issued session token should include the same session_id as the old token
99
+
100
+ :param old_token: old session token data
98
101
  :param refresh_token: encoded refresh token
99
102
  :return: new session token, new refresh token
100
103
  """
dara/core/auth/routes.py CHANGED
@@ -19,7 +19,15 @@ from inspect import iscoroutinefunction
19
19
  from typing import Union, cast
20
20
 
21
21
  import jwt
22
- from fastapi import APIRouter, Cookie, Depends, HTTPException, Request, Response
22
+ from fastapi import (
23
+ APIRouter,
24
+ BackgroundTasks,
25
+ Cookie,
26
+ Depends,
27
+ HTTPException,
28
+ Request,
29
+ Response,
30
+ )
23
31
  from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
24
32
 
25
33
  from dara.core.auth.base import BaseAuthConfig
@@ -32,6 +40,7 @@ from dara.core.auth.definitions import (
32
40
  AuthError,
33
41
  SessionRequestBody,
34
42
  )
43
+ from dara.core.auth.utils import decode_token
35
44
  from dara.core.logging import dev_logger
36
45
 
37
46
  auth_router = APIRouter()
@@ -107,7 +116,9 @@ async def _revoke_session(response: Response, credentials: HTTPAuthorizationCred
107
116
  @auth_router.post('/refresh-token')
108
117
  async def handle_refresh_token(
109
118
  response: Response,
119
+ background_tasks: BackgroundTasks,
110
120
  dara_refresh_token: Union[str, None] = Cookie(default=None),
121
+ credentials: HTTPAuthorizationCredentials = Depends(HTTPBearer()),
111
122
  ):
112
123
  """
113
124
  Given a refresh token, issues a new session token and refresh token cookie.
@@ -119,13 +130,42 @@ async def handle_refresh_token(
119
130
  if dara_refresh_token is None:
120
131
  raise HTTPException(status_code=400, detail=BAD_REQUEST_ERROR('No refresh token provided'))
121
132
 
122
- from dara.core.internal.registries import auth_registry
133
+ # Check scheme is correct
134
+ if credentials.scheme != 'Bearer':
135
+ raise HTTPException(
136
+ status_code=400,
137
+ detail=BAD_REQUEST_ERROR(
138
+ 'Invalid authentication scheme, previous Bearer token must be included in the refresh request'
139
+ ),
140
+ )
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
123
148
 
124
149
  auth_config: BaseAuthConfig = auth_registry.get('auth_config')
150
+ ws_manager: WebsocketManager = utils_registry.get('WebsocketManager')
125
151
 
126
152
  try:
127
- # Refresh logic up to implementation
128
- session_token, refresh_token = auth_config.refresh_token(dara_refresh_token)
153
+ # decode the old token ignoring expiry date
154
+ old_token_data = decode_token(credentials.credentials, options={'verify_exp': False})
155
+
156
+ # Refresh logic up to implementation - passing in old token data so session_id can be preserved
157
+ session_token, refresh_token = auth_config.refresh_token(old_token_data, dara_refresh_token)
158
+
159
+ # Notify the active websocket handlers (i.e. active connections, per each tab open)
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)
129
169
 
130
170
  # Using 'Strict' as it is only used for the refresh-token endpoint so cross-site requests are not expected
131
171
  response.set_cookie(
dara/core/auth/utils.py CHANGED
@@ -33,12 +33,15 @@ from dara.core.internal.settings import get_settings
33
33
  from dara.core.logging import dev_logger
34
34
 
35
35
 
36
- def decode_token(token: str) -> TokenData:
36
+ def decode_token(token: str, **kwargs) -> TokenData:
37
37
  """
38
38
  Decode a JWT token
39
+
40
+ :param token: the JWT token to decode
41
+ :param kwargs: additional arguments to pass to the jwt.decode function
39
42
  """
40
43
  try:
41
- return TokenData.parse_obj(jwt.decode(token, get_settings().jwt_secret, algorithms=[JWT_ALGO]))
44
+ return TokenData.parse_obj(jwt.decode(token, get_settings().jwt_secret, algorithms=[JWT_ALGO], **kwargs))
42
45
  except jwt.ExpiredSignatureError:
43
46
  raise AuthError(code=401, detail=EXPIRED_TOKEN_ERROR)
44
47
  except jwt.DecodeError:
@@ -52,11 +55,13 @@ def sign_jwt(
52
55
  groups: List[str],
53
56
  id_token: Optional[str] = None,
54
57
  exp: Optional[Union[datetime, int]] = None,
58
+ session_id: Optional[str] = None,
55
59
  ):
56
60
  """
57
61
  Create a new Dara JWT token
58
62
  """
59
- session_id = str(uuid.uuid4())
63
+ if session_id is None:
64
+ session_id = str(uuid.uuid4())
60
65
 
61
66
  # Default expiry is 1 day unless specified
62
67
  if exp is None:
@@ -208,9 +208,11 @@ def create_router(config: Configuration):
208
208
  'application_name': get_settings().project_name,
209
209
  }
210
210
 
211
- @core_api_router.get('/auth-components')
212
- async def get_auth_components(): # pylint: disable=unused-variable
213
- return config.auth_config.component_config
211
+ @core_api_router.get('/auth-config')
212
+ async def get_auth_config(): # pylint: disable=unused-variable
213
+ return {
214
+ 'auth_components': config.auth_config.component_config.dict(),
215
+ }
214
216
 
215
217
  @core_api_router.get('/components', dependencies=[Depends(verify_session)])
216
218
  async def get_components(name: Optional[str] = None): # pylint: disable=unused-variable
@@ -156,6 +156,16 @@ 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
+
159
169
  pending_responses: Dict[str, Tuple[Event, Optional[Any]]]
160
170
  """
161
171
  A map of pending responses from the client. The key is the message ID and the value is a tuple of the event to
@@ -167,11 +177,38 @@ class WebSocketHandler:
167
177
 
168
178
  def __init__(self, channel_id: str):
169
179
  send_stream, receive_stream = create_memory_object_stream[ServerMessage](math.inf)
170
- self.channel_id = channel_id
171
- self.send_stream = send_stream
172
180
  self.receive_stream = receive_stream
181
+ self.send_stream = send_stream
182
+
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
+ self.channel_id = channel_id
173
188
  self.pending_responses = {}
174
189
 
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
+
175
212
  async def send_message(self, message: ServerMessage):
176
213
  """
177
214
  Send a message to the client.
@@ -446,17 +483,20 @@ async def ws_handler(websocket: WebSocket, token: Optional[str] = Query(default=
446
483
  else:
447
484
  sessions_registry.set(user_identifier, {token_content.session_id})
448
485
 
449
- # Set Auth context vars for the WS connection
450
- USER.set(
451
- UserData(
452
- identity_id=token_content.identity_id,
453
- identity_name=token_content.identity_name,
454
- identity_email=token_content.identity_email,
455
- groups=token_content.groups,
486
+ def update_context(token_data: TokenData):
487
+ USER.set(
488
+ UserData(
489
+ identity_id=token_data.identity_id,
490
+ identity_name=token_data.identity_name,
491
+ identity_email=token_data.identity_email,
492
+ groups=token_data.groups,
493
+ )
456
494
  )
457
- )
458
- SESSION_ID.set(token_content.session_id)
459
- ID_TOKEN.set(token_content.id_token)
495
+ SESSION_ID.set(token_data.session_id)
496
+ ID_TOKEN.set(token_data.id_token)
497
+
498
+ # Set initial Auth context vars for the WS connection
499
+ update_context(token_content)
460
500
 
461
501
  # Change protocol from http to ws - from this point exceptions can't be raised
462
502
  await websocket.accept()
@@ -488,6 +528,10 @@ async def ws_handler(websocket: WebSocket, token: Optional[str] = Query(default=
488
528
  # as the latter does not properly handle disconnections e.g. when relaoading the server
489
529
  data = await websocket.receive_json()
490
530
 
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
+
491
535
  # Heartbeat to keep connection alive
492
536
  if data['type'] == 'ping':
493
537
  await websocket.send_json({'type': 'pong', 'message': None})
@@ -54981,8 +54981,8 @@ You must set sticky: 'left' | 'right' for the '${bugWithUnderColumnsSticky.Heade
54981
54981
  return __privateGet(this, _state)[key];
54982
54982
  }
54983
54983
  setValue(key, value) {
54984
- __privateMethod(this, _notify, notify_fn).call(this, key, value);
54985
54984
  __privateGet(this, _state)[key] = value;
54985
+ __privateMethod(this, _notify, notify_fn).call(this, key, value);
54986
54986
  }
54987
54987
  async replaceValue(key, fn) {
54988
54988
  if (__privateGet(this, _locks)[key]) {
@@ -54998,10 +54998,8 @@ You must set sticky: 'left' | 'right' for the '${bugWithUnderColumnsSticky.Heade
54998
54998
  let result;
54999
54999
  try {
55000
55000
  result = await fn();
55001
- __privateGet(this, _state)[key] = result;
55002
- __privateMethod(this, _notify, notify_fn).call(this, key, result);
55003
- unlock(result);
55004
55001
  this.setValue(key, result);
55002
+ unlock(result);
55005
55003
  } catch (e3) {
55006
55004
  unlockError(e3);
55007
55005
  } finally {
@@ -56141,11 +56139,11 @@ You must set sticky: 'left' | 'right' for the '${bugWithUnderColumnsSticky.Heade
56141
56139
  message.reason
56142
56140
  );
56143
56141
  }
56144
- function useAuthComponents() {
56142
+ function useAuthConfig() {
56145
56143
  return reactQuery.useQuery(
56146
- ["auth-components"],
56144
+ ["auth-config"],
56147
56145
  async () => {
56148
- const response = await request("/api/core/auth-components", {
56146
+ const response = await request("/api/core/auth-config", {
56149
56147
  method: HTTP_METHOD.GET
56150
56148
  });
56151
56149
  return response.json();
@@ -59120,7 +59118,7 @@ Inferred class string: "${iconClasses}."`
59120
59118
  return component;
59121
59119
  }
59122
59120
  function AuthWrapper(props) {
59123
- const { data: authComponents, isLoading } = useAuthComponents();
59121
+ const { data: authConfig, isLoading } = useAuthConfig();
59124
59122
  const isMounted = React.useRef(false);
59125
59123
  if (!isMounted.current) {
59126
59124
  isMounted.current = true;
@@ -59139,7 +59137,7 @@ Inferred class string: "${iconClasses}."`
59139
59137
  if (isLoading) {
59140
59138
  return /* @__PURE__ */ React__default.default.createElement(Center, null, /* @__PURE__ */ React__default.default.createElement(DefaultFallback, null));
59141
59139
  }
59142
- const { login, logout, ...extraRoutes } = authComponents;
59140
+ const { login, logout, ...extraRoutes } = authConfig.auth_components;
59143
59141
  return /* @__PURE__ */ React__default.default.createElement(Switch$1, null, /* @__PURE__ */ React__default.default.createElement(Route, { path: "/login" }, /* @__PURE__ */ React__default.default.createElement(DynamicAuthComponent, { component: login })), /* @__PURE__ */ React__default.default.createElement(Route, { path: "/logout" }, /* @__PURE__ */ React__default.default.createElement(DynamicAuthComponent, { component: logout })), Object.entries(extraRoutes).map(([path, component]) => /* @__PURE__ */ React__default.default.createElement(Route, { key: path, path: `/${path}` }, /* @__PURE__ */ React__default.default.createElement(DynamicAuthComponent, { component }))), /* @__PURE__ */ React__default.default.createElement(Route, { component: ErrorPage, path: "/error" }), /* @__PURE__ */ React__default.default.createElement(Route, { path: "/", render: () => /* @__PURE__ */ React__default.default.createElement(PrivateRoute, null, props.children) }));
59144
59142
  }
59145
59143
  const index = "";
@@ -86454,6 +86452,7 @@ Inferred class string: "${iconClasses}."`
86454
86452
  exports.combineFilters = combineFilters;
86455
86453
  exports.default = run;
86456
86454
  exports.getIcon = getIcon;
86455
+ exports.getSessionToken = getSessionToken;
86457
86456
  exports.getToken = getToken;
86458
86457
  exports.getTokenKey = getTokenKey;
86459
86458
  exports.handleAuthErrors = handleAuthErrors;
@@ -86464,6 +86463,7 @@ Inferred class string: "${iconClasses}."`
86464
86463
  exports.request = request;
86465
86464
  exports.resolveValue = resolveValue;
86466
86465
  exports.revokeSession = revokeSession;
86466
+ exports.setSessionToken = setSessionToken;
86467
86467
  exports.useAction = useAction;
86468
86468
  exports.useActionIsLoading = useActionIsLoading;
86469
86469
  exports.useAnyVariable = useAnyVariable;
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dara-core
3
- Version: 1.14.0a0
3
+ Version: 1.14.0a2
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.0)
23
+ Requires-Dist: create-dara-app (==1.14.0-alpha.2)
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.0) ; extra == "all"
26
+ Requires-Dist: dara-components (==1.14.0-alpha.2) ; 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.0/img/dara_light.svg?raw=true">
54
+ <img src="https://github.com/causalens/dara/blob/v1.14.0-alpha.2/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.14.0-alpha.0/img/components_gallery.png?raw=true)
99
+ ![Dara App](https://github.com/causalens/dara/blob/v1.14.0-alpha.2/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.14.0-alpha.0/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.14.0-alpha.0/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.14.0-alpha.0/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.14.0-alpha.2/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.14.0-alpha.2/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.14.0-alpha.2/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
 
@@ -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.0/CONTRIBUTING.md) file.
145
+ More information on the repository structure can be found in the [CONTRIBUTING.md](https://github.com/causalens/dara/blob/v1.14.0-alpha.2/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.0/LICENSE).
149
+ Dara is open-source and licensed under the [Apache 2.0 License](https://github.com/causalens/dara/blob/v1.14.0-alpha.2/LICENSE).
150
150
 
@@ -1,11 +1,11 @@
1
1
  dara/core/__init__.py,sha256=w3OUU03lGXA45_Sf99CsNWL6SZ7tr7y4wRYq6pdGmhM,2186
2
2
  dara/core/actions.py,sha256=gARcrrtzYuBAVJUCtuHwpFc6PPVPb7x3ITIISCLw0GA,965
3
3
  dara/core/auth/__init__.py,sha256=H0bJoXff5wIRZmHvvQ3y9p5SXA9lM8OuLCGceYGqfb0,851
4
- dara/core/auth/base.py,sha256=OYMS29YP8uSqnUhgSddN2mGyfIztJjhsmPWrsX12-UI,3034
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=ShujHmVZ7jMvnp5ETnTRSPzKRdsnBRH-qsCRwq5Nf4Q,6467
8
- dara/core/auth/utils.py,sha256=DKIXFRj9YeAjo3Oh2PCdqlSW7vMuHsyVqMEuJ0kpNxM,2855
7
+ dara/core/auth/routes.py,sha256=x3oFLOkgi7-r0mqFD0GgM3hdS1JkmLHWQ6mNnEKRDDk,7951
8
+ dara/core/auth/utils.py,sha256=ngOi5j71Xu-G59yWxGoejBEmMnVyGS67aF7czt_0i7A,3062
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
@@ -54,13 +54,13 @@ dara/core/internal/port_utils.py,sha256=AQOUNiFNBYKVUwQ7i9UlY1NQ3sWb5xh5GkO6P1Bm
54
54
  dara/core/internal/registries.py,sha256=9WDczIsNeSmzi6aViIq_b14lmmYGGkdsUGHpv0Sg9zo,3278
55
55
  dara/core/internal/registry.py,sha256=ONCDusqaL0q59Py_r8-fFVN3vbkkDf5TXzNvbB9SrGQ,4305
56
56
  dara/core/internal/registry_lookup.py,sha256=8snHu2wUUsngXjHyHh6eZqL_WwonTTQB6-WBX-R_WZg,2238
57
- dara/core/internal/routing.py,sha256=dEnuK0zp9-4SLsK5_2M3NUKzROpDItPwMfBPnDA2MYY,22739
57
+ dara/core/internal/routing.py,sha256=Wdy11iWDeAf2PyHXZv5mPeJ_BEcoR0XftOl3M8vUhJU,22782
58
58
  dara/core/internal/scheduler.py,sha256=z6OYwazBf3GYo8CzMC9IuGC2P96gI7JwxquT8GaoTMk,12944
59
59
  dara/core/internal/settings.py,sha256=wAWxl-HXjq7PW3twe_CrR-UuMRw9VBudC3eRmevZAhM,3869
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=dhvE7KnolBHwPy9E6Ac1tjzOn0fAtdhDdcMUs47WuuQ,20138
63
+ dara/core/internal/websocket.py,sha256=i6QXWHfcWbQDT3dk9F_7yw5CNhkC-lnqMoH7EKHBqC4,21676
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=bJedHgo9fFmh31_bRtCpuDPGjhz29_JKPXo8ExpqdKI,4877903
84
+ dara/core/umd/dara.core.umd.js,sha256=yJvT54abv74o8fFuSoeH1NucuJVl90UFfeM3ZsCp3c8,4877860
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.0a0.dist-info/LICENSE,sha256=r9u1w2RvpLMV6YjuXHIKXRBKzia3fx_roPwboGcLqCc,10944
109
- dara_core-1.14.0a0.dist-info/METADATA,sha256=GL9mjDN9_67kKik1JGInnVH8YFgTKzlgtgLM8Tyz7Bo,7457
110
- dara_core-1.14.0a0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
111
- dara_core-1.14.0a0.dist-info/entry_points.txt,sha256=H__D5sNIGuPIhVam0DChNL-To5k8Y7nY7TAFz9Mz6cc,139
112
- dara_core-1.14.0a0.dist-info/RECORD,,
108
+ dara_core-1.14.0a2.dist-info/LICENSE,sha256=r9u1w2RvpLMV6YjuXHIKXRBKzia3fx_roPwboGcLqCc,10944
109
+ dara_core-1.14.0a2.dist-info/METADATA,sha256=RLDwxR63x52m9wlBKPA8P5Eg8s8bKifiPIybYPcpTT0,7457
110
+ dara_core-1.14.0a2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
111
+ dara_core-1.14.0a2.dist-info/entry_points.txt,sha256=H__D5sNIGuPIhVam0DChNL-To5k8Y7nY7TAFz9Mz6cc,139
112
+ dara_core-1.14.0a2.dist-info/RECORD,,