dara-core 1.19.0__py3-none-any.whl → 1.20.0__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.
Files changed (52) hide show
  1. dara/core/__init__.py +1 -0
  2. dara/core/auth/basic.py +13 -7
  3. dara/core/auth/definitions.py +2 -2
  4. dara/core/auth/utils.py +1 -1
  5. dara/core/base_definitions.py +7 -42
  6. dara/core/data_utils.py +16 -17
  7. dara/core/definitions.py +8 -8
  8. dara/core/interactivity/__init__.py +6 -0
  9. dara/core/interactivity/actions.py +26 -22
  10. dara/core/interactivity/any_data_variable.py +7 -135
  11. dara/core/interactivity/any_variable.py +1 -1
  12. dara/core/interactivity/client_variable.py +71 -0
  13. dara/core/interactivity/data_variable.py +8 -266
  14. dara/core/interactivity/derived_data_variable.py +6 -290
  15. dara/core/interactivity/derived_variable.py +381 -201
  16. dara/core/interactivity/filtering.py +29 -2
  17. dara/core/interactivity/loop_variable.py +2 -2
  18. dara/core/interactivity/non_data_variable.py +5 -68
  19. dara/core/interactivity/plain_variable.py +87 -14
  20. dara/core/interactivity/server_variable.py +325 -0
  21. dara/core/interactivity/state_variable.py +69 -0
  22. dara/core/interactivity/switch_variable.py +15 -15
  23. dara/core/interactivity/tabular_variable.py +94 -0
  24. dara/core/interactivity/url_variable.py +10 -90
  25. dara/core/internal/cache_store/cache_store.py +5 -20
  26. dara/core/internal/dependency_resolution.py +27 -69
  27. dara/core/internal/devtools.py +10 -3
  28. dara/core/internal/execute_action.py +9 -3
  29. dara/core/internal/multi_resource_lock.py +70 -0
  30. dara/core/internal/normalization.py +0 -5
  31. dara/core/internal/pandas_utils.py +105 -3
  32. dara/core/internal/pool/definitions.py +1 -1
  33. dara/core/internal/pool/task_pool.py +9 -6
  34. dara/core/internal/pool/utils.py +19 -14
  35. dara/core/internal/registries.py +3 -2
  36. dara/core/internal/registry.py +1 -1
  37. dara/core/internal/registry_lookup.py +5 -3
  38. dara/core/internal/routing.py +52 -121
  39. dara/core/internal/store.py +2 -29
  40. dara/core/internal/tasks.py +372 -182
  41. dara/core/internal/utils.py +25 -3
  42. dara/core/internal/websocket.py +1 -1
  43. dara/core/js_tooling/js_utils.py +2 -0
  44. dara/core/logging.py +10 -6
  45. dara/core/persistence.py +26 -4
  46. dara/core/umd/dara.core.umd.js +1091 -1469
  47. dara/core/visual/dynamic_component.py +17 -13
  48. {dara_core-1.19.0.dist-info → dara_core-1.20.0.dist-info}/METADATA +11 -11
  49. {dara_core-1.19.0.dist-info → dara_core-1.20.0.dist-info}/RECORD +52 -47
  50. {dara_core-1.19.0.dist-info → dara_core-1.20.0.dist-info}/LICENSE +0 -0
  51. {dara_core-1.19.0.dist-info → dara_core-1.20.0.dist-info}/WHEEL +0 -0
  52. {dara_core-1.19.0.dist-info → dara_core-1.20.0.dist-info}/entry_points.txt +0 -0
@@ -33,13 +33,16 @@ from typing import (
33
33
  Literal,
34
34
  Optional,
35
35
  Tuple,
36
+ Type,
37
+ TypeVar,
36
38
  Union,
37
39
  )
38
40
 
39
41
  import anyio
40
42
  from anyio import from_thread
41
- from exceptiongroup import ExceptionGroup
43
+ from exceptiongroup import BaseExceptionGroup, ExceptionGroup
42
44
  from starlette.concurrency import run_in_threadpool
45
+ from typing_extensions import ParamSpec
43
46
 
44
47
  from dara.core.auth.definitions import SESSION_ID, USER
45
48
  from dara.core.base_definitions import CacheType
@@ -176,7 +179,11 @@ def enforce_sso(conf: ConfigurationBuilder):
176
179
  ) from err
177
180
 
178
181
 
179
- def async_dedupe(fn: Callable[..., Awaitable]):
182
+ P = ParamSpec('P')
183
+ T = TypeVar('T')
184
+
185
+
186
+ def async_dedupe(fn: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
180
187
  """
181
188
  Decorator to deduplicate concurrent calls to asynchronous functions based on their arguments.
182
189
 
@@ -194,7 +201,7 @@ def async_dedupe(fn: Callable[..., Awaitable]):
194
201
  is_method = 'self' in inspect.signature(fn).parameters
195
202
 
196
203
  @wraps(fn)
197
- async def wrapped(*args, **kwargs):
204
+ async def wrapped(*args: P.args, **kwargs: P.kwargs) -> T:
198
205
  non_self_args = args[1:] if is_method else args
199
206
  key = (non_self_args, frozenset(kwargs.items()))
200
207
  lock = locks.get(key)
@@ -234,3 +241,18 @@ def resolve_exception_group(error: Any):
234
241
  return resolve_exception_group(error.exceptions[0])
235
242
 
236
243
  return error
244
+
245
+
246
+ def exception_group_contains(err_type: Type[BaseException], group: BaseExceptionGroup) -> bool:
247
+ """
248
+ Check if an ExceptionGroup contains an error of a given type, recursively
249
+
250
+ :param err_type: The type of error to check for
251
+ :param group: The ExceptionGroup to check
252
+ """
253
+ for exc in group.exceptions:
254
+ if isinstance(exc, err_type):
255
+ return True
256
+ if isinstance(exc, BaseExceptionGroup):
257
+ return exception_group_contains(err_type, exc)
258
+ return False
@@ -474,7 +474,7 @@ async def ws_handler(websocket: WebSocket, token: Optional[str] = Query(default=
474
474
  if pending_tokens_registry.has(token):
475
475
  pending_tokens_registry.remove(token)
476
476
 
477
- user_identifier = token_content.identity_id or token_content.identity_name
477
+ user_identifier = token_content.identity_id
478
478
 
479
479
  # Add the new session ID to known sessions for that user
480
480
  if sessions_registry.has(user_identifier):
@@ -544,11 +544,13 @@ def bundle_js(build_cache: BuildCache, copy_js: bool = False):
544
544
 
545
545
  cwd = os.getcwd()
546
546
  os.chdir(build_cache.static_files_dir)
547
+ dev_logger.info('Installing JS dependencies...')
547
548
  exit_code = os.system(f'{package_manager} install') # nosec B605 # package_manager is validated
548
549
  if exit_code > 0:
549
550
  raise SystemError(
550
551
  "Failed to install the JS dependencies - there's likely a connection issue or a broken package"
551
552
  )
553
+ dev_logger.info('JS dependencies installed successfully')
552
554
 
553
555
  # Load entry template as a string
554
556
  with open(entry_template, encoding='utf-8') as f:
dara/core/logging.py CHANGED
@@ -74,7 +74,7 @@ class Logger:
74
74
 
75
75
  self._logger.warning(payload, extra={'content': extra})
76
76
 
77
- def error(self, title: str, error: Exception, extra: Optional[Dict[str, Any]] = None):
77
+ def error(self, title: str, error: BaseException, extra: Optional[Dict[str, Any]] = None):
78
78
  """
79
79
  Log a message at the ERROR level
80
80
 
@@ -171,11 +171,14 @@ class LoggingMiddleware(BaseHTTPMiddleware):
171
171
  return response
172
172
 
173
173
 
174
- def _print_stacktrace():
174
+ def _print_stacktrace(err: Optional[BaseException] = None) -> str:
175
175
  """
176
176
  Prints out the current stack trace whilst unwinding all the logging calls on the way so it just shows the relevant
177
177
  parts. Will also extract any exceptions and print them at the end.
178
178
  """
179
+ if err is not None:
180
+ return ''.join(traceback.format_exception(type(err), err, err.__traceback__))
181
+
179
182
  exc = sys.exc_info()[0]
180
183
  stack = traceback.extract_stack()[:-1]
181
184
  if exc is not None:
@@ -215,8 +218,9 @@ class DaraProdFormatter(logging.Formatter):
215
218
  }
216
219
 
217
220
  if record.levelname == 'ERROR':
218
- payload['error'] = str(payload.pop('error'))
219
- payload['stacktrace'] = _print_stacktrace()
221
+ err = payload.pop('error')
222
+ payload['error'] = str(err)
223
+ payload['stacktrace'] = _print_stacktrace(err if isinstance(err, BaseException) else None)
220
224
 
221
225
  return payload
222
226
 
@@ -281,8 +285,8 @@ class DaraDevFormatter(logging.Formatter):
281
285
 
282
286
  if record.levelname == 'ERROR':
283
287
  error = ''
284
- if payload.get('error'):
285
- error = _print_stacktrace()
288
+ if err := payload.get('error'):
289
+ error = _print_stacktrace(err if isinstance(err, BaseException) else None)
286
290
  content = base_msg
287
291
  if record.__dict__.get('content'):
288
292
  content = content + '\r\n' + self.extra_template % (spacer, record.__dict__['content'])
dara/core/persistence.py CHANGED
@@ -255,7 +255,7 @@ class BackendStore(PersistenceStore):
255
255
  user = USER.get()
256
256
 
257
257
  if user:
258
- user_key = user.identity_id or user.identity_name
258
+ user_key = user.identity_id
259
259
 
260
260
  # Make sure the store is initialized
261
261
  if user_key not in self.initialized_scopes:
@@ -277,7 +277,7 @@ class BackendStore(PersistenceStore):
277
277
  if key == 'global':
278
278
  return None
279
279
 
280
- # otherwise key is a user identity_id or identity_name
280
+ # otherwise key is a user identity_id
281
281
  return key
282
282
 
283
283
  def _register(self):
@@ -374,7 +374,7 @@ class BackendStore(PersistenceStore):
374
374
  if not user:
375
375
  return
376
376
 
377
- user_identifier = user.identity_id or user.identity_name
377
+ user_identifier = user.identity_id
378
378
  return await self._notify_user(user_identifier, value=value, ignore_channel=ignore_channel)
379
379
 
380
380
  async def _notify_patches(self, patches: List[Dict[str, Any]]):
@@ -393,7 +393,7 @@ class BackendStore(PersistenceStore):
393
393
  if not user:
394
394
  return
395
395
 
396
- user_identifier = user.identity_id or user.identity_name
396
+ user_identifier = user.identity_id
397
397
  return await self._notify_user(user_identifier, patches=patches)
398
398
 
399
399
  async def init(self, variable: 'Variable'):
@@ -540,3 +540,25 @@ class BackendStoreEntry(BaseModel):
540
540
  uid: str
541
541
  store: BackendStore
542
542
  """Store instance"""
543
+
544
+
545
+ class BrowserStore(PersistenceStore):
546
+ """
547
+ Persistence store implementation that uses browser local storage
548
+ """
549
+
550
+ async def init(self, variable: 'Variable'):
551
+ # noop
552
+ pass
553
+
554
+
555
+ class QueryParamStore(PersistenceStore):
556
+ """
557
+ Persistence store implementation that uses a URL query parameter
558
+ """
559
+
560
+ query: str
561
+
562
+ async def init(self, variable: 'Variable'):
563
+ # noop
564
+ pass