dara-core 1.20.1a1__py3-none-any.whl → 1.20.1a2__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 (82) hide show
  1. dara/core/__init__.py +3 -0
  2. dara/core/actions.py +1 -2
  3. dara/core/auth/basic.py +22 -16
  4. dara/core/auth/definitions.py +2 -2
  5. dara/core/auth/routes.py +5 -5
  6. dara/core/auth/utils.py +5 -5
  7. dara/core/base_definitions.py +22 -64
  8. dara/core/cli.py +8 -7
  9. dara/core/configuration.py +5 -2
  10. dara/core/css.py +1 -2
  11. dara/core/data_utils.py +18 -19
  12. dara/core/defaults.py +6 -7
  13. dara/core/definitions.py +50 -19
  14. dara/core/http.py +7 -3
  15. dara/core/interactivity/__init__.py +6 -0
  16. dara/core/interactivity/actions.py +52 -50
  17. dara/core/interactivity/any_data_variable.py +7 -134
  18. dara/core/interactivity/any_variable.py +5 -8
  19. dara/core/interactivity/client_variable.py +71 -0
  20. dara/core/interactivity/data_variable.py +8 -266
  21. dara/core/interactivity/derived_data_variable.py +7 -290
  22. dara/core/interactivity/derived_variable.py +416 -176
  23. dara/core/interactivity/filtering.py +46 -27
  24. dara/core/interactivity/loop_variable.py +2 -2
  25. dara/core/interactivity/non_data_variable.py +5 -68
  26. dara/core/interactivity/plain_variable.py +89 -15
  27. dara/core/interactivity/server_variable.py +325 -0
  28. dara/core/interactivity/state_variable.py +69 -0
  29. dara/core/interactivity/switch_variable.py +19 -19
  30. dara/core/interactivity/tabular_variable.py +94 -0
  31. dara/core/interactivity/url_variable.py +10 -90
  32. dara/core/internal/cache_store/base_impl.py +2 -1
  33. dara/core/internal/cache_store/cache_store.py +22 -25
  34. dara/core/internal/cache_store/keep_all.py +4 -1
  35. dara/core/internal/cache_store/lru.py +5 -1
  36. dara/core/internal/cache_store/ttl.py +4 -1
  37. dara/core/internal/cgroup.py +1 -1
  38. dara/core/internal/dependency_resolution.py +60 -66
  39. dara/core/internal/devtools.py +12 -5
  40. dara/core/internal/download.py +13 -4
  41. dara/core/internal/encoder_registry.py +7 -7
  42. dara/core/internal/execute_action.py +13 -13
  43. dara/core/internal/hashing.py +1 -3
  44. dara/core/internal/import_discovery.py +3 -4
  45. dara/core/internal/multi_resource_lock.py +70 -0
  46. dara/core/internal/normalization.py +9 -18
  47. dara/core/internal/pandas_utils.py +107 -5
  48. dara/core/internal/pool/definitions.py +1 -1
  49. dara/core/internal/pool/task_pool.py +25 -16
  50. dara/core/internal/pool/utils.py +21 -18
  51. dara/core/internal/pool/worker.py +3 -2
  52. dara/core/internal/port_utils.py +1 -1
  53. dara/core/internal/registries.py +12 -6
  54. dara/core/internal/registry.py +4 -2
  55. dara/core/internal/registry_lookup.py +11 -5
  56. dara/core/internal/routing.py +109 -145
  57. dara/core/internal/scheduler.py +13 -8
  58. dara/core/internal/settings.py +2 -2
  59. dara/core/internal/store.py +2 -29
  60. dara/core/internal/tasks.py +379 -195
  61. dara/core/internal/utils.py +36 -13
  62. dara/core/internal/websocket.py +21 -20
  63. dara/core/js_tooling/js_utils.py +28 -26
  64. dara/core/js_tooling/templates/vite.config.template.ts +12 -3
  65. dara/core/logging.py +13 -12
  66. dara/core/main.py +14 -11
  67. dara/core/metrics/cache.py +1 -1
  68. dara/core/metrics/utils.py +3 -3
  69. dara/core/persistence.py +27 -5
  70. dara/core/umd/dara.core.umd.js +68291 -64718
  71. dara/core/visual/components/__init__.py +2 -2
  72. dara/core/visual/components/fallback.py +30 -4
  73. dara/core/visual/components/for_cmp.py +4 -1
  74. dara/core/visual/css/__init__.py +30 -31
  75. dara/core/visual/dynamic_component.py +31 -28
  76. dara/core/visual/progress_updater.py +4 -3
  77. {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a2.dist-info}/METADATA +12 -11
  78. dara_core-1.20.1a2.dist-info/RECORD +119 -0
  79. dara_core-1.20.1a1.dist-info/RECORD +0 -114
  80. {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a2.dist-info}/LICENSE +0 -0
  81. {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a2.dist-info}/WHEEL +0 -0
  82. {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a2.dist-info}/entry_points.txt +0 -0
dara/core/__init__.py CHANGED
@@ -14,6 +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
+ # ruff: noqa: F403, F405
18
+
17
19
  from importlib.metadata import version
18
20
 
19
21
  from pydantic import BaseModel
@@ -39,6 +41,7 @@ __all__ = [
39
41
  'DerivedVariable',
40
42
  'DerivedDataVariable',
41
43
  'DataVariable',
44
+ 'ServerVariable',
42
45
  'UrlVariable',
43
46
  'Cache',
44
47
  'CacheType',
dara/core/actions.py CHANGED
@@ -16,8 +16,7 @@ limitations under the License.
16
16
  """
17
17
 
18
18
  # Re-export actions so users can import from dara.core.actions instead of dara_core.interactivity
19
- # pylint: disable=unused-import
20
- from dara.core.interactivity import (
19
+ from dara.core.interactivity import ( # noqa: F401
21
20
  DownloadContent,
22
21
  DownloadContentImpl,
23
22
  DownloadVariable,
dara/core/auth/basic.py CHANGED
@@ -25,7 +25,6 @@ from dara.core.auth.definitions import (
25
25
  EXPIRED_TOKEN_ERROR,
26
26
  INVALID_CREDENTIALS_ERROR,
27
27
  INVALID_TOKEN_ERROR,
28
- JWT_ALGO,
29
28
  SESSION_ID,
30
29
  USER,
31
30
  AuthError,
@@ -35,7 +34,6 @@ from dara.core.auth.definitions import (
35
34
  UserData,
36
35
  )
37
36
  from dara.core.auth.utils import decode_token, sign_jwt
38
- from dara.core.internal.settings import get_settings
39
37
 
40
38
  DefaultAuthLogin = AuthComponent(js_module='@darajs/core', py_module='dara.core', js_name='DefaultAuthLogin')
41
39
 
@@ -69,7 +67,7 @@ class BaseBasicAuthConfig(BaseAuthConfig):
69
67
 
70
68
  return {
71
69
  'token': sign_jwt(
72
- identity_id=None,
70
+ identity_id=body.username,
73
71
  identity_name=body.username,
74
72
  identity_email=None,
75
73
  groups=[],
@@ -87,14 +85,15 @@ class BaseBasicAuthConfig(BaseAuthConfig):
87
85
  SESSION_ID.set(decoded.session_id)
88
86
  USER.set(
89
87
  UserData(
88
+ identity_id=decoded.identity_name,
90
89
  identity_name=decoded.identity_name,
91
90
  )
92
91
  )
93
92
  return decoded
94
- except jwt.ExpiredSignatureError:
95
- raise AuthError(EXPIRED_TOKEN_ERROR, 401)
96
- except jwt.DecodeError:
97
- raise AuthError(INVALID_TOKEN_ERROR, 401)
93
+ except jwt.ExpiredSignatureError as e:
94
+ raise AuthError(EXPIRED_TOKEN_ERROR, 401) from e
95
+ except jwt.DecodeError as e:
96
+ raise AuthError(INVALID_TOKEN_ERROR, 401) from e
98
97
 
99
98
 
100
99
  class BasicAuthConfig(BaseBasicAuthConfig):
@@ -121,13 +120,13 @@ class DefaultAuthConfig(BaseAuthConfig):
121
120
  logout=BasicAuthLogout,
122
121
  )
123
122
 
124
- def get_token(self, _: SessionRequestBody) -> TokenResponse:
123
+ def get_token(self, body: SessionRequestBody) -> TokenResponse:
125
124
  """
126
125
  Get a session token.
127
126
 
128
127
  In default auth a new token is returned every time.
129
128
  """
130
- token = sign_jwt(identity_id=None, identity_name='user', identity_email=None, groups=[])
129
+ token = sign_jwt(identity_id='user', identity_name='user', identity_email=None, groups=[])
131
130
  return {'token': token}
132
131
 
133
132
  def verify_token(self, token: str) -> TokenData:
@@ -139,10 +138,17 @@ class DefaultAuthConfig(BaseAuthConfig):
139
138
  :param token: the token to verify
140
139
  """
141
140
  try:
142
- decoded = jwt.decode(token, get_settings().jwt_secret, algorithms=[JWT_ALGO])
143
- SESSION_ID.set(decoded.get('session_id'))
144
- return TokenData.parse_obj(decoded)
145
- except jwt.ExpiredSignatureError:
146
- raise AuthError(EXPIRED_TOKEN_ERROR, 401)
147
- except jwt.DecodeError:
148
- raise AuthError(INVALID_TOKEN_ERROR, 401)
141
+ decoded = decode_token(token)
142
+ SESSION_ID.set(decoded.session_id)
143
+ # Implicit auth assumes used by one user so all users are the same
144
+ USER.set(
145
+ UserData(
146
+ identity_id=decoded.identity_id,
147
+ identity_name=decoded.identity_id,
148
+ )
149
+ )
150
+ return decoded
151
+ except jwt.ExpiredSignatureError as e:
152
+ raise AuthError(EXPIRED_TOKEN_ERROR, 401) from e
153
+ except jwt.DecodeError as e:
154
+ raise AuthError(INVALID_TOKEN_ERROR, 401) from e
@@ -39,7 +39,7 @@ class TokenData(BaseModel):
39
39
 
40
40
  session_id: str
41
41
  exp: Union[float, int, datetime]
42
- identity_id: Optional[str] = None
42
+ identity_id: str
43
43
  identity_name: str
44
44
  identity_email: Optional[str] = None
45
45
  id_token: Optional[str] = None
@@ -56,7 +56,7 @@ class UserData(BaseModel):
56
56
  :param groups: list of groups user belongs to
57
57
  """
58
58
 
59
- identity_id: Optional[str] = None
59
+ identity_id: str
60
60
  identity_name: str
61
61
  identity_email: Optional[str] = None
62
62
  groups: Optional[List[str]] = []
dara/core/auth/routes.py CHANGED
@@ -75,13 +75,13 @@ async def verify_session(
75
75
  return SESSION_ID.get()
76
76
  except jwt.ExpiredSignatureError as e:
77
77
  dev_logger.error('Expired Token Signature', error=e)
78
- raise HTTPException(status_code=401, detail=EXPIRED_TOKEN_ERROR)
78
+ raise HTTPException(status_code=401, detail=EXPIRED_TOKEN_ERROR) from e
79
79
  except jwt.PyJWTError as e:
80
80
  dev_logger.error('Invalid Token', error=e)
81
- raise HTTPException(status_code=401, detail=INVALID_TOKEN_ERROR)
81
+ raise HTTPException(status_code=401, detail=INVALID_TOKEN_ERROR) from e
82
82
  except AuthError as err:
83
83
  dev_logger.error('Auth Error', error=err)
84
- raise HTTPException(status_code=err.code, detail=err.detail)
84
+ raise HTTPException(status_code=err.code, detail=err.detail) from err
85
85
  raise HTTPException(status_code=400, detail=BAD_REQUEST_ERROR('No auth credentials passed'))
86
86
 
87
87
 
@@ -162,11 +162,11 @@ async def handle_refresh_token(
162
162
  # Explicitly handle expired signature error
163
163
  if isinstance(e, jwt.ExpiredSignatureError):
164
164
  dev_logger.error('Expired Token Signature', error=e)
165
- raise HTTPException(status_code=401, detail=EXPIRED_TOKEN_ERROR, headers=headers)
165
+ raise HTTPException(status_code=401, detail=EXPIRED_TOKEN_ERROR, headers=headers) from e
166
166
 
167
167
  # Otherwise show a generic invalid token error
168
168
  dev_logger.error('Invalid Token', error=cast(Exception, e))
169
- raise HTTPException(status_code=401, detail=INVALID_TOKEN_ERROR, headers=headers)
169
+ raise HTTPException(status_code=401, detail=INVALID_TOKEN_ERROR, headers=headers) from e
170
170
 
171
171
 
172
172
  # Request to retrieve a session token from the backend. The app does this on startup.
dara/core/auth/utils.py CHANGED
@@ -44,14 +44,14 @@ def decode_token(token: str, **kwargs) -> TokenData:
44
44
  """
45
45
  try:
46
46
  return TokenData.parse_obj(jwt.decode(token, get_settings().jwt_secret, algorithms=[JWT_ALGO], **kwargs))
47
- except jwt.ExpiredSignatureError:
48
- raise AuthError(code=401, detail=EXPIRED_TOKEN_ERROR)
49
- except jwt.DecodeError:
50
- raise AuthError(code=401, detail=INVALID_TOKEN_ERROR)
47
+ except jwt.ExpiredSignatureError as e:
48
+ raise AuthError(code=401, detail=EXPIRED_TOKEN_ERROR) from e
49
+ except jwt.DecodeError as e:
50
+ raise AuthError(code=401, detail=INVALID_TOKEN_ERROR) from e
51
51
 
52
52
 
53
53
  def sign_jwt(
54
- identity_id: Optional[str],
54
+ identity_id: str,
55
55
  identity_name: str,
56
56
  identity_email: Optional[str],
57
57
  groups: List[str],
@@ -21,17 +21,16 @@ from __future__ import annotations
21
21
  # between other parts of the framework
22
22
  import abc
23
23
  import uuid
24
+ from collections.abc import Awaitable, Mapping
24
25
  from enum import Enum
25
26
  from typing import (
26
27
  TYPE_CHECKING,
27
28
  Annotated,
28
29
  Any,
29
- Awaitable,
30
30
  Callable,
31
31
  ClassVar,
32
32
  Dict,
33
33
  List,
34
- Mapping,
35
34
  Optional,
36
35
  Tuple,
37
36
  Union,
@@ -67,7 +66,7 @@ def annotation_has_base_model(typ: Any) -> bool:
67
66
  type_args = get_args(typ)
68
67
  if len(type_args) > 0:
69
68
  return any(annotation_has_base_model(arg) for arg in type_args)
70
- except: # pylint: disable=bare-except
69
+ except: # noqa: E722
71
70
  # canot get arguments, should be a simple type
72
71
  pass
73
72
 
@@ -79,9 +78,7 @@ def annotation_has_base_model(typ: Any) -> bool:
79
78
  # It works by adding SerializeAsAny to all fields of the model.
80
79
  # See https://github.com/pydantic/pydantic/issues/6381
81
80
  class SerializeAsAnyMeta(ModelMetaclass):
82
- def __new__(
83
- self, name: str, bases: Tuple[type], namespaces: Dict[str, Any], **kwargs
84
- ): # pylint: disable=bad-mcs-classmethod-argument
81
+ def __new__(cls, name: str, bases: Tuple[type], namespaces: Dict[str, Any], **kwargs):
85
82
  annotations: dict = namespaces.get('__annotations__', {}).copy()
86
83
 
87
84
  for base in bases:
@@ -97,11 +94,11 @@ class SerializeAsAnyMeta(ModelMetaclass):
97
94
  if isinstance(annotation, str) or annotation is ClassVar:
98
95
  continue
99
96
  if annotation_has_base_model(annotation):
100
- annotations[field] = SerializeAsAny[annotation] # type: ignore
97
+ annotations[field] = SerializeAsAny[annotation] # type: ignore
101
98
 
102
99
  namespaces['__annotations__'] = annotations
103
100
 
104
- return super().__new__(self, name, bases, namespaces, **kwargs)
101
+ return super().__new__(cls, name, bases, namespaces, **kwargs)
105
102
 
106
103
 
107
104
  class DaraBaseModel(BaseModel, metaclass=SerializeAsAnyMeta):
@@ -135,16 +132,17 @@ class DaraBaseModel(BaseModel, metaclass=SerializeAsAnyMeta):
135
132
  and annotation_has_base_model(field_info.annotation)
136
133
  ):
137
134
  # Skip if it has metadata that is already annotated with SerializeAsAny
138
- if any(isinstance(x, SerializeAsAny) for x in field_info.metadata): # type: ignore
135
+ if any(isinstance(x, SerializeAsAny) for x in field_info.metadata): # type: ignore
139
136
  continue
140
137
  # Skip if the type is already annotated with SerializeAsAny
141
138
  if get_origin(field_info.annotation) is Annotated and any(
142
- isinstance(arg, SerializeAsAny) for arg in field_info.annotation.__metadata__ # type: ignore
139
+ isinstance(arg, SerializeAsAny) # pyright: ignore[reportArgumentType]
140
+ for arg in field_info.annotation.__metadata__ # type: ignore
143
141
  ):
144
142
  continue
145
143
 
146
- field_info.annotation = SerializeAsAny[field_info.annotation] # type: ignore
147
- field_info.metadata = list(field_info.annotation.__metadata__) # type: ignore
144
+ field_info.annotation = SerializeAsAny[field_info.annotation] # type: ignore
145
+ field_info.metadata = list(field_info.annotation.__metadata__) # type: ignore
148
146
 
149
147
  # Rebuild again with force to ensure we rebuild the schema with new annotations
150
148
  return super().model_rebuild(
@@ -255,10 +253,8 @@ class Cache:
255
253
  if isinstance(arg, Cache.Type):
256
254
  return LruCachePolicy(cache_type=arg)
257
255
 
258
- if isinstance(arg, str):
259
- # Check that the string is one of allowed cache members
260
- if typ := Cache.Type.get_member(arg):
261
- return LruCachePolicy(cache_type=typ)
256
+ if isinstance(arg, str) and (typ := Cache.Type.get_member(arg)):
257
+ return LruCachePolicy(cache_type=typ)
262
258
 
263
259
  raise ValueError(
264
260
  f'Invalid cache argument: {arg}. Please provide a Cache.Policy object or one of Cache.Type members'
@@ -308,6 +304,12 @@ class CachedRegistryEntry(BaseModel):
308
304
  return f'{self.__class__.__name__}(cache={self.cache}, uid={self.uid})'
309
305
 
310
306
 
307
+ class NonTabularDataError(Exception):
308
+ """
309
+ Raised when trying to interpret a non-tabular variable as tabular
310
+ """
311
+
312
+
311
313
  class BaseTaskMessage(BaseModel):
312
314
  task_id: str
313
315
 
@@ -349,12 +351,10 @@ class BaseTask(abc.ABC):
349
351
  super().__init__()
350
352
 
351
353
  @abc.abstractmethod
352
- async def run(self, send_stream: Optional[MemoryObjectSendStream[TaskMessage]] = None) -> Any:
353
- ...
354
+ async def run(self, send_stream: Optional[MemoryObjectSendStream[TaskMessage]] = None) -> Any: ...
354
355
 
355
356
  @abc.abstractmethod
356
- async def cancel(self):
357
- ...
357
+ async def cancel(self): ...
358
358
 
359
359
 
360
360
  class PendingTask(BaseTask):
@@ -413,7 +413,7 @@ class PendingTask(BaseTask):
413
413
  self.cancel_scope.cancel()
414
414
  await self.task_def.cancel()
415
415
 
416
- self.error = Exception('Task was cancelled')
416
+ self.error = anyio.get_cancelled_exc_class()()
417
417
  self.event.set()
418
418
 
419
419
  def add_subscriber(self):
@@ -438,47 +438,6 @@ class PendingTask(BaseTask):
438
438
  return self.result
439
439
 
440
440
 
441
- class PendingValue:
442
- """
443
- An internal class that's used to represent a pending value. Holds a future object that can be awaited by
444
- multiple consumers.
445
- """
446
-
447
- def __init__(self):
448
- self.event = anyio.Event()
449
- self._value = None
450
- self._error = None
451
-
452
- async def wait(self):
453
- """
454
- Wait for the underlying event to be set
455
- """
456
- # Waiting in chunks as otherwise Jupyter blocks the event loop
457
- while not self.event.is_set():
458
- await anyio.sleep(0.01)
459
- if self._error:
460
- raise self._error
461
- return self._value
462
-
463
- def resolve(self, value: Any):
464
- """
465
- Resolve the pending state and send values to the waiting code
466
-
467
- :param value: the value to resolve as the result
468
- """
469
- self._value = value
470
- self.event.set()
471
-
472
- def error(self, exc: Exception):
473
- """
474
- Resolve the pending state with an error and send it to the waiting code
475
-
476
- :param exc: exception to resolve as the result
477
- """
478
- self._error = exc
479
- self.event.set()
480
-
481
-
482
441
  class AnnotatedAction(BaseModel):
483
442
  """
484
443
  Represents a single call to an @action-annotated action.
@@ -504,12 +463,11 @@ class AnnotatedAction(BaseModel):
504
463
  dynamic_kwargs: Mapping[str, Any]
505
464
  """Dynamic kwargs of the action; uid -> variable instance"""
506
465
 
507
- loading: 'Variable' # type: ignore
466
+ loading: Variable # type: ignore # noqa: F821
508
467
  """Loading Variable instance"""
509
468
 
510
469
  def __init__(self, **data):
511
470
  # Resolve the circular dependency to add a loading Variable to the model upon creation
512
- # pylint: disable-next=import-error, import-outside-toplevel
513
471
  from dara.core.interactivity.plain_variable import Variable
514
472
 
515
473
  self.model_rebuild()
dara/core/cli.py CHANGED
@@ -21,10 +21,10 @@ import pathlib
21
21
  import subprocess
22
22
  from typing import List, Optional
23
23
 
24
- import click
25
24
  import uvicorn
26
- from click.exceptions import UsageError
27
25
 
26
+ import click
27
+ from click.exceptions import UsageError
28
28
  from dara.core.internal.port_utils import find_available_port
29
29
  from dara.core.internal.settings import generate_env_file
30
30
  from dara.core.internal.utils import find_module_path
@@ -52,7 +52,7 @@ def cli():
52
52
  @click.option('--port', help='The port to run on', type=int)
53
53
  @click.option('--metrics-port', help='The port for the metrics server to run on', type=int)
54
54
  @click.option('--disable-metrics', is_flag=True, help='Whether to disable the metrics server')
55
- @click.option('--host', default='0.0.0.0', help='The host to run on') # nosec B104 # default for local dev
55
+ @click.option('--host', default='0.0.0.0', help='The host to run on') # nosec B104 # default for local dev
56
56
  @click.option('--rebuild', is_flag=True, help='Whether to force a rebuild of the app')
57
57
  @click.option('--require-sso', is_flag=True, help='Whether to enforce that an SSO auth config is used')
58
58
  @click.option('--docker', is_flag=True, help='Whether to run in Docker mode - assumes assets are prebuilt')
@@ -214,10 +214,11 @@ def dev():
214
214
  stderr=subprocess.STDOUT,
215
215
  shell=True,
216
216
  ) as vite_process:
217
- for line in vite_process.stdout:
218
- decoded_line = line.decode('utf-8').strip()
219
- if decoded_line != '':
220
- print(decoded_line) # pylint: disable=bad-builtin
217
+ if vite_process.stdout is not None:
218
+ for line in vite_process.stdout:
219
+ decoded_line = line.decode('utf-8').strip()
220
+ if decoded_line != '':
221
+ print(decoded_line)
221
222
 
222
223
 
223
224
  @cli.command()
@@ -14,6 +14,7 @@ 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
18
  import os
18
19
  import pathlib
19
20
  from inspect import isclass, isfunction
@@ -386,7 +387,7 @@ class ConfigurationBuilder:
386
387
  icon: Optional[str] = None,
387
388
  route: Optional[str] = None,
388
389
  include_in_menu: Optional[bool] = True,
389
- reset_vars_on_load: Optional[List[AnyVariable]] = [],
390
+ reset_vars_on_load: Optional[List[AnyVariable]] = None,
390
391
  on_load: Optional[Action] = None,
391
392
  ):
392
393
  """
@@ -402,6 +403,8 @@ class ConfigurationBuilder:
402
403
  :param on_load: optional action to execute upon visiting the page
403
404
  """
404
405
  # Backwards compatibility - deprecated
406
+ if reset_vars_on_load is None:
407
+ reset_vars_on_load = []
405
408
  if reset_vars_on_load is not None and len(reset_vars_on_load) > 0:
406
409
  if on_load is not None:
407
410
  raise ValueError('reset_vars_on_load and on_load cannot be used together')
@@ -485,7 +488,7 @@ class ConfigurationBuilder:
485
488
  if len(options) > 0:
486
489
  dev_logger.warning(f'Options provided for a function middleware {middleware}, but they will be ignored')
487
490
  elif isclass(middleware):
488
- constructed_middleware = Middleware(middleware, **options) # type: ignore
491
+ constructed_middleware = Middleware(middleware, **options) # type: ignore
489
492
  else:
490
493
  raise ValueError(f'Invalid middleware type: {type(middleware)}')
491
494
 
dara/core/css.py CHANGED
@@ -18,8 +18,7 @@ limitations under the License.
18
18
  from typing import Literal, Optional
19
19
 
20
20
  # Re-export CSSProperties for easier importing
21
- # pylint: disable=unused-import
22
- from dara.core.visual.css import CSSProperties
21
+ from dara.core.visual.css import CSSProperties # noqa: F401
23
22
 
24
23
  IconStyle = Literal['solid', 'regular', 'brands']
25
24
  IconSize = Literal['1x', '2x', '3x', '4x', '5x', '6x', '7x', '8x', '9x', '10x', '2xs', 'xs', 'sm', 'lg', 'xl', '2xl']
dara/core/data_utils.py CHANGED
@@ -25,10 +25,9 @@ from pandas import DataFrame
25
25
  from dara.core.base_definitions import CacheType
26
26
  from dara.core.base_definitions import DaraBaseModel as BaseModel
27
27
  from dara.core.interactivity import (
28
- DerivedDataVariable,
28
+ ClientVariable,
29
29
  DerivedVariable,
30
30
  DownloadContent,
31
- NonDataVariable,
32
31
  SideEffect,
33
32
  Variable,
34
33
  )
@@ -116,7 +115,7 @@ class FileStore(BaseModel):
116
115
  if not os.path.exists(file_path):
117
116
  return None
118
117
 
119
- return io.open(file_path, 'rb')
118
+ return open(file_path, 'rb')
120
119
 
121
120
  def write_file(self, cache_type: CacheType, name: str) -> io.BufferedWriter:
122
121
  """
@@ -130,7 +129,7 @@ class FileStore(BaseModel):
130
129
  """
131
130
  scope_path = self.get_scoped_path(cache_type)
132
131
  os.makedirs(scope_path, exist_ok=True)
133
- return io.open(os.path.join(scope_path, name), 'wb')
132
+ return open(os.path.join(scope_path, name), 'wb')
134
133
 
135
134
  def delete_file(self, cache_type: CacheType, name: str) -> None:
136
135
  """
@@ -172,7 +171,7 @@ class DataFactory(BaseModel):
172
171
 
173
172
  def list_datasets_var(
174
173
  self,
175
- cache: Union[CacheType, NonDataVariable] = CacheType.GLOBAL,
174
+ cache: Union[CacheType, ClientVariable] = CacheType.GLOBAL,
176
175
  polling_interval: Optional[int] = None,
177
176
  ) -> DerivedVariable[List[str]]:
178
177
  """
@@ -181,7 +180,7 @@ class DataFactory(BaseModel):
181
180
  :param cache: cache type to get the list of datasets for
182
181
  :param polling_interval: optional polling_interval in seconds for the derived variable
183
182
  """
184
- cache_var = cache if isinstance(cache, NonDataVariable) else Variable(cache)
183
+ cache_var = cache if isinstance(cache, ClientVariable) else Variable(cache)
185
184
 
186
185
  return DerivedVariable(
187
186
  lambda cache_val: self.file_store.list_files(cache_val or CacheType.GLOBAL),
@@ -289,21 +288,21 @@ class DataFactory(BaseModel):
289
288
 
290
289
  def read_dataset_var(
291
290
  self,
292
- name: Union[str, NonDataVariable],
293
- cache: Union[CacheType, NonDataVariable] = CacheType.GLOBAL,
291
+ name: Union[str, ClientVariable],
292
+ cache: Union[CacheType, ClientVariable] = CacheType.GLOBAL,
294
293
  polling_interval: Optional[int] = None,
295
- ) -> DerivedDataVariable:
294
+ ) -> DerivedVariable:
296
295
  """
297
- Create a DerivedDataVariable which reads a specific dataset from disk
296
+ Create a DerivedVariable which reads a specific dataset from disk
298
297
 
299
298
  :param name: name of the dataset
300
299
  :param cache: cache to get the dataset for
301
300
  :param polling_interval: optional polling interval in seconds for the derived variable
302
301
  """
303
- name_var = name if isinstance(name, NonDataVariable) else Variable(name)
304
- cache_var = cache if isinstance(cache, NonDataVariable) else Variable(cache)
302
+ name_var = name if isinstance(name, ClientVariable) else Variable(name)
303
+ cache_var = cache if isinstance(cache, ClientVariable) else Variable(cache)
305
304
 
306
- return DerivedDataVariable(
305
+ return DerivedVariable(
307
306
  self.read_dataset,
308
307
  variables=[name_var, cache_var],
309
308
  cache=CacheType.SESSION,
@@ -320,7 +319,7 @@ class DataFactory(BaseModel):
320
319
  self.file_store.delete_file(cache, name)
321
320
 
322
321
  def delete_dataset_action(
323
- self, name: Union[str, NonDataVariable], cache: Union[CacheType, NonDataVariable] = CacheType.GLOBAL
322
+ self, name: Union[str, ClientVariable], cache: Union[CacheType, ClientVariable] = CacheType.GLOBAL
324
323
  ):
325
324
  """
326
325
  Get a SideEffect action which deletes a given dataset
@@ -328,13 +327,13 @@ class DataFactory(BaseModel):
328
327
  :param name: name of the dataset
329
328
  :param cache: cache to remove the daatset for
330
329
  """
331
- name_var = name if isinstance(name, NonDataVariable) else Variable(name)
332
- cache_var = cache if isinstance(cache, NonDataVariable) else Variable(cache)
330
+ name_var = name if isinstance(name, ClientVariable) else Variable(name)
331
+ cache_var = cache if isinstance(cache, ClientVariable) else Variable(cache)
333
332
 
334
333
  return SideEffect(lambda ctx: self.delete_dataset(ctx.extras[0], ctx.extras[1]), extras=[name_var, cache_var])
335
334
 
336
335
  def download_dataset_action(
337
- self, name: Union[str, NonDataVariable], cache: Union[CacheType, NonDataVariable] = CacheType.GLOBAL
336
+ self, name: Union[str, ClientVariable], cache: Union[CacheType, ClientVariable] = CacheType.GLOBAL
338
337
  ):
339
338
  """
340
339
  Get a DownloadContent action which downloads a dataset with a given name as a .csv
@@ -342,8 +341,8 @@ class DataFactory(BaseModel):
342
341
  :param name: name of the dataset to download
343
342
  :param cache: cache to download dataset for
344
343
  """
345
- name_var = name if isinstance(name, NonDataVariable) else Variable(name)
346
- cache_var = cache if isinstance(cache, NonDataVariable) else Variable(cache)
344
+ name_var = name if isinstance(name, ClientVariable) else Variable(name)
345
+ cache_var = cache if isinstance(cache, ClientVariable) else Variable(cache)
347
346
 
348
347
  def _resolver(ctx: DownloadContent.Ctx): # type: ignore
349
348
  ds_name, sel_cache = ctx.extras # type: ignore
dara/core/defaults.py CHANGED
@@ -17,7 +17,7 @@ limitations under the License.
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- from typing import TYPE_CHECKING, Dict
20
+ from typing import TYPE_CHECKING, Dict, cast
21
21
 
22
22
  from dara.core.base_definitions import ActionDef
23
23
  from dara.core.interactivity.actions import (
@@ -53,6 +53,7 @@ from dara.core.visual.components import (
53
53
  TopBarFrame,
54
54
  TopBarFrameDef,
55
55
  )
56
+ from dara.core.visual.components.fallback import CustomFallbackDef
56
57
  from dara.core.visual.template import TemplateBuilder
57
58
 
58
59
  if TYPE_CHECKING:
@@ -77,8 +78,9 @@ CORE_COMPONENTS: Dict[str, ComponentTypeAnnotation] = {
77
78
  RouterContent.__name__: RouterContentDef,
78
79
  SideBarFrame.__name__: SideBarFrameDef,
79
80
  TopBarFrame.__name__: TopBarFrameDef,
80
- Fallback.Default.py_component: DefaultFallbackDef,
81
- Fallback.Row.py_component: RowFallbackDef,
81
+ cast(str, Fallback.Default.py_component): DefaultFallbackDef,
82
+ cast(str, Fallback.Row.py_component): RowFallbackDef,
83
+ cast(str, Fallback.Custom.py_component): CustomFallbackDef,
82
84
  For.__name__: ForDef,
83
85
  }
84
86
 
@@ -93,9 +95,9 @@ CORE_ACTIONS: Dict[str, ActionDef] = {
93
95
  Notify.__name__: NotifyDef,
94
96
  }
95
97
 
98
+
96
99
  # Define a default layout template
97
100
  def default_template(config: Configuration) -> Template:
98
-
99
101
  template = TemplateBuilder(name='default')
100
102
 
101
103
  router = template.add_router_from_pages(list(config.pages.values()))
@@ -107,7 +109,6 @@ def default_template(config: Configuration) -> Template:
107
109
 
108
110
  # Define a blank template
109
111
  def blank_template(config: Configuration) -> Template:
110
-
111
112
  template = TemplateBuilder(name='default')
112
113
 
113
114
  router = template.add_router_from_pages(list(config.pages.values()))
@@ -119,7 +120,6 @@ def blank_template(config: Configuration) -> Template:
119
120
 
120
121
  # Define a top layout template
121
122
  def top_template(config: Configuration) -> Template:
122
-
123
123
  template = TemplateBuilder(name='default')
124
124
 
125
125
  router = template.add_router_from_pages(list(config.pages.values()))
@@ -131,7 +131,6 @@ def top_template(config: Configuration) -> Template:
131
131
 
132
132
  # Define a top layout template with menu
133
133
  def top_menu_template(config: Configuration) -> Template:
134
-
135
134
  template = TemplateBuilder(name='default')
136
135
 
137
136
  router = template.add_router_from_pages(list(config.pages.values()))