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.
- dara/core/__init__.py +3 -0
- dara/core/actions.py +1 -2
- dara/core/auth/basic.py +22 -16
- dara/core/auth/definitions.py +2 -2
- dara/core/auth/routes.py +5 -5
- dara/core/auth/utils.py +5 -5
- dara/core/base_definitions.py +22 -64
- dara/core/cli.py +8 -7
- dara/core/configuration.py +5 -2
- dara/core/css.py +1 -2
- dara/core/data_utils.py +18 -19
- dara/core/defaults.py +6 -7
- dara/core/definitions.py +50 -19
- dara/core/http.py +7 -3
- dara/core/interactivity/__init__.py +6 -0
- dara/core/interactivity/actions.py +52 -50
- dara/core/interactivity/any_data_variable.py +7 -134
- dara/core/interactivity/any_variable.py +5 -8
- dara/core/interactivity/client_variable.py +71 -0
- dara/core/interactivity/data_variable.py +8 -266
- dara/core/interactivity/derived_data_variable.py +7 -290
- dara/core/interactivity/derived_variable.py +416 -176
- dara/core/interactivity/filtering.py +46 -27
- dara/core/interactivity/loop_variable.py +2 -2
- dara/core/interactivity/non_data_variable.py +5 -68
- dara/core/interactivity/plain_variable.py +89 -15
- dara/core/interactivity/server_variable.py +325 -0
- dara/core/interactivity/state_variable.py +69 -0
- dara/core/interactivity/switch_variable.py +19 -19
- dara/core/interactivity/tabular_variable.py +94 -0
- dara/core/interactivity/url_variable.py +10 -90
- dara/core/internal/cache_store/base_impl.py +2 -1
- dara/core/internal/cache_store/cache_store.py +22 -25
- dara/core/internal/cache_store/keep_all.py +4 -1
- dara/core/internal/cache_store/lru.py +5 -1
- dara/core/internal/cache_store/ttl.py +4 -1
- dara/core/internal/cgroup.py +1 -1
- dara/core/internal/dependency_resolution.py +60 -66
- dara/core/internal/devtools.py +12 -5
- dara/core/internal/download.py +13 -4
- dara/core/internal/encoder_registry.py +7 -7
- dara/core/internal/execute_action.py +13 -13
- dara/core/internal/hashing.py +1 -3
- dara/core/internal/import_discovery.py +3 -4
- dara/core/internal/multi_resource_lock.py +70 -0
- dara/core/internal/normalization.py +9 -18
- dara/core/internal/pandas_utils.py +107 -5
- dara/core/internal/pool/definitions.py +1 -1
- dara/core/internal/pool/task_pool.py +25 -16
- dara/core/internal/pool/utils.py +21 -18
- dara/core/internal/pool/worker.py +3 -2
- dara/core/internal/port_utils.py +1 -1
- dara/core/internal/registries.py +12 -6
- dara/core/internal/registry.py +4 -2
- dara/core/internal/registry_lookup.py +11 -5
- dara/core/internal/routing.py +109 -145
- dara/core/internal/scheduler.py +13 -8
- dara/core/internal/settings.py +2 -2
- dara/core/internal/store.py +2 -29
- dara/core/internal/tasks.py +379 -195
- dara/core/internal/utils.py +36 -13
- dara/core/internal/websocket.py +21 -20
- dara/core/js_tooling/js_utils.py +28 -26
- dara/core/js_tooling/templates/vite.config.template.ts +12 -3
- dara/core/logging.py +13 -12
- dara/core/main.py +14 -11
- dara/core/metrics/cache.py +1 -1
- dara/core/metrics/utils.py +3 -3
- dara/core/persistence.py +27 -5
- dara/core/umd/dara.core.umd.js +68291 -64718
- dara/core/visual/components/__init__.py +2 -2
- dara/core/visual/components/fallback.py +30 -4
- dara/core/visual/components/for_cmp.py +4 -1
- dara/core/visual/css/__init__.py +30 -31
- dara/core/visual/dynamic_component.py +31 -28
- dara/core/visual/progress_updater.py +4 -3
- {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a2.dist-info}/METADATA +12 -11
- dara_core-1.20.1a2.dist-info/RECORD +119 -0
- dara_core-1.20.1a1.dist-info/RECORD +0 -114
- {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a2.dist-info}/LICENSE +0 -0
- {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a2.dist-info}/WHEEL +0 -0
- {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
|
-
#
|
|
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=
|
|
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,
|
|
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=
|
|
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 =
|
|
143
|
-
SESSION_ID.set(decoded.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
dara/core/auth/definitions.py
CHANGED
|
@@ -39,7 +39,7 @@ class TokenData(BaseModel):
|
|
|
39
39
|
|
|
40
40
|
session_id: str
|
|
41
41
|
exp: Union[float, int, datetime]
|
|
42
|
-
identity_id:
|
|
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:
|
|
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:
|
|
54
|
+
identity_id: str,
|
|
55
55
|
identity_name: str,
|
|
56
56
|
identity_email: Optional[str],
|
|
57
57
|
groups: List[str],
|
dara/core/base_definitions.py
CHANGED
|
@@ -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:
|
|
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]
|
|
97
|
+
annotations[field] = SerializeAsAny[annotation] # type: ignore
|
|
101
98
|
|
|
102
99
|
namespaces['__annotations__'] = annotations
|
|
103
100
|
|
|
104
|
-
return super().__new__(
|
|
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):
|
|
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)
|
|
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]
|
|
147
|
-
field_info.metadata = list(field_info.annotation.__metadata__)
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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')
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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()
|
dara/core/configuration.py
CHANGED
|
@@ -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)
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
293
|
-
cache: Union[CacheType,
|
|
291
|
+
name: Union[str, ClientVariable],
|
|
292
|
+
cache: Union[CacheType, ClientVariable] = CacheType.GLOBAL,
|
|
294
293
|
polling_interval: Optional[int] = None,
|
|
295
|
-
) ->
|
|
294
|
+
) -> DerivedVariable:
|
|
296
295
|
"""
|
|
297
|
-
Create a
|
|
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,
|
|
304
|
-
cache_var = cache if isinstance(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
|
|
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,
|
|
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,
|
|
332
|
-
cache_var = cache if isinstance(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,
|
|
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,
|
|
346
|
-
cache_var = cache if isinstance(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()))
|