dara-core 1.20.0__py3-none-any.whl → 1.20.1a1__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 +0 -3
- dara/core/actions.py +2 -1
- dara/core/auth/basic.py +16 -22
- 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 +64 -22
- dara/core/cli.py +7 -8
- dara/core/configuration.py +2 -5
- dara/core/css.py +2 -1
- dara/core/data_utils.py +19 -18
- dara/core/defaults.py +7 -6
- dara/core/definitions.py +19 -50
- dara/core/http.py +3 -7
- dara/core/interactivity/__init__.py +0 -6
- dara/core/interactivity/actions.py +50 -52
- dara/core/interactivity/any_data_variable.py +134 -7
- dara/core/interactivity/any_variable.py +8 -5
- dara/core/interactivity/data_variable.py +266 -8
- dara/core/interactivity/derived_data_variable.py +290 -7
- dara/core/interactivity/derived_variable.py +176 -416
- dara/core/interactivity/filtering.py +27 -46
- dara/core/interactivity/loop_variable.py +2 -2
- dara/core/interactivity/non_data_variable.py +68 -5
- dara/core/interactivity/plain_variable.py +15 -89
- dara/core/interactivity/switch_variable.py +19 -19
- dara/core/interactivity/url_variable.py +90 -10
- dara/core/internal/cache_store/base_impl.py +1 -2
- dara/core/internal/cache_store/cache_store.py +25 -22
- dara/core/internal/cache_store/keep_all.py +1 -4
- dara/core/internal/cache_store/lru.py +1 -5
- dara/core/internal/cache_store/ttl.py +1 -4
- dara/core/internal/cgroup.py +1 -1
- dara/core/internal/dependency_resolution.py +66 -60
- dara/core/internal/devtools.py +5 -12
- dara/core/internal/download.py +4 -13
- dara/core/internal/encoder_registry.py +7 -7
- dara/core/internal/execute_action.py +13 -13
- dara/core/internal/hashing.py +3 -1
- dara/core/internal/import_discovery.py +4 -3
- dara/core/internal/normalization.py +18 -9
- dara/core/internal/pandas_utils.py +5 -107
- dara/core/internal/pool/definitions.py +1 -1
- dara/core/internal/pool/task_pool.py +16 -25
- dara/core/internal/pool/utils.py +18 -21
- dara/core/internal/pool/worker.py +2 -3
- dara/core/internal/port_utils.py +1 -1
- dara/core/internal/registries.py +6 -12
- dara/core/internal/registry.py +2 -4
- dara/core/internal/registry_lookup.py +5 -11
- dara/core/internal/routing.py +145 -109
- dara/core/internal/scheduler.py +8 -13
- dara/core/internal/settings.py +2 -2
- dara/core/internal/store.py +29 -2
- dara/core/internal/tasks.py +195 -379
- dara/core/internal/utils.py +13 -36
- dara/core/internal/websocket.py +20 -21
- dara/core/js_tooling/js_utils.py +26 -28
- dara/core/js_tooling/templates/vite.config.template.ts +3 -12
- dara/core/logging.py +12 -13
- dara/core/main.py +11 -14
- dara/core/metrics/cache.py +1 -1
- dara/core/metrics/utils.py +3 -3
- dara/core/persistence.py +5 -27
- dara/core/umd/dara.core.umd.js +55428 -59098
- dara/core/visual/components/__init__.py +2 -2
- dara/core/visual/components/fallback.py +4 -30
- dara/core/visual/components/for_cmp.py +1 -4
- dara/core/visual/css/__init__.py +31 -30
- dara/core/visual/dynamic_component.py +28 -31
- dara/core/visual/progress_updater.py +3 -4
- {dara_core-1.20.0.dist-info → dara_core-1.20.1a1.dist-info}/METADATA +11 -12
- dara_core-1.20.1a1.dist-info/RECORD +114 -0
- dara/core/interactivity/client_variable.py +0 -71
- dara/core/interactivity/server_variable.py +0 -325
- dara/core/interactivity/state_variable.py +0 -69
- dara/core/interactivity/tabular_variable.py +0 -94
- dara/core/internal/multi_resource_lock.py +0 -70
- dara_core-1.20.0.dist-info/RECORD +0 -119
- {dara_core-1.20.0.dist-info → dara_core-1.20.1a1.dist-info}/LICENSE +0 -0
- {dara_core-1.20.0.dist-info → dara_core-1.20.1a1.dist-info}/WHEEL +0 -0
- {dara_core-1.20.0.dist-info → dara_core-1.20.1a1.dist-info}/entry_points.txt +0 -0
dara/core/__init__.py
CHANGED
|
@@ -14,8 +14,6 @@ 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
|
-
|
|
19
17
|
from importlib.metadata import version
|
|
20
18
|
|
|
21
19
|
from pydantic import BaseModel
|
|
@@ -41,7 +39,6 @@ __all__ = [
|
|
|
41
39
|
'DerivedVariable',
|
|
42
40
|
'DerivedDataVariable',
|
|
43
41
|
'DataVariable',
|
|
44
|
-
'ServerVariable',
|
|
45
42
|
'UrlVariable',
|
|
46
43
|
'Cache',
|
|
47
44
|
'CacheType',
|
dara/core/actions.py
CHANGED
|
@@ -16,7 +16,8 @@ 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
|
-
|
|
19
|
+
# pylint: disable=unused-import
|
|
20
|
+
from dara.core.interactivity import (
|
|
20
21
|
DownloadContent,
|
|
21
22
|
DownloadContentImpl,
|
|
22
23
|
DownloadVariable,
|
dara/core/auth/basic.py
CHANGED
|
@@ -25,6 +25,7 @@ from dara.core.auth.definitions import (
|
|
|
25
25
|
EXPIRED_TOKEN_ERROR,
|
|
26
26
|
INVALID_CREDENTIALS_ERROR,
|
|
27
27
|
INVALID_TOKEN_ERROR,
|
|
28
|
+
JWT_ALGO,
|
|
28
29
|
SESSION_ID,
|
|
29
30
|
USER,
|
|
30
31
|
AuthError,
|
|
@@ -34,6 +35,7 @@ from dara.core.auth.definitions import (
|
|
|
34
35
|
UserData,
|
|
35
36
|
)
|
|
36
37
|
from dara.core.auth.utils import decode_token, sign_jwt
|
|
38
|
+
from dara.core.internal.settings import get_settings
|
|
37
39
|
|
|
38
40
|
DefaultAuthLogin = AuthComponent(js_module='@darajs/core', py_module='dara.core', js_name='DefaultAuthLogin')
|
|
39
41
|
|
|
@@ -67,7 +69,7 @@ class BaseBasicAuthConfig(BaseAuthConfig):
|
|
|
67
69
|
|
|
68
70
|
return {
|
|
69
71
|
'token': sign_jwt(
|
|
70
|
-
identity_id=
|
|
72
|
+
identity_id=None,
|
|
71
73
|
identity_name=body.username,
|
|
72
74
|
identity_email=None,
|
|
73
75
|
groups=[],
|
|
@@ -85,15 +87,14 @@ class BaseBasicAuthConfig(BaseAuthConfig):
|
|
|
85
87
|
SESSION_ID.set(decoded.session_id)
|
|
86
88
|
USER.set(
|
|
87
89
|
UserData(
|
|
88
|
-
identity_id=decoded.identity_name,
|
|
89
90
|
identity_name=decoded.identity_name,
|
|
90
91
|
)
|
|
91
92
|
)
|
|
92
93
|
return decoded
|
|
93
|
-
except jwt.ExpiredSignatureError
|
|
94
|
-
raise AuthError(EXPIRED_TOKEN_ERROR, 401)
|
|
95
|
-
except jwt.DecodeError
|
|
96
|
-
raise AuthError(INVALID_TOKEN_ERROR, 401)
|
|
94
|
+
except jwt.ExpiredSignatureError:
|
|
95
|
+
raise AuthError(EXPIRED_TOKEN_ERROR, 401)
|
|
96
|
+
except jwt.DecodeError:
|
|
97
|
+
raise AuthError(INVALID_TOKEN_ERROR, 401)
|
|
97
98
|
|
|
98
99
|
|
|
99
100
|
class BasicAuthConfig(BaseBasicAuthConfig):
|
|
@@ -120,13 +121,13 @@ class DefaultAuthConfig(BaseAuthConfig):
|
|
|
120
121
|
logout=BasicAuthLogout,
|
|
121
122
|
)
|
|
122
123
|
|
|
123
|
-
def get_token(self,
|
|
124
|
+
def get_token(self, _: SessionRequestBody) -> TokenResponse:
|
|
124
125
|
"""
|
|
125
126
|
Get a session token.
|
|
126
127
|
|
|
127
128
|
In default auth a new token is returned every time.
|
|
128
129
|
"""
|
|
129
|
-
token = sign_jwt(identity_id=
|
|
130
|
+
token = sign_jwt(identity_id=None, identity_name='user', identity_email=None, groups=[])
|
|
130
131
|
return {'token': token}
|
|
131
132
|
|
|
132
133
|
def verify_token(self, token: str) -> TokenData:
|
|
@@ -138,17 +139,10 @@ class DefaultAuthConfig(BaseAuthConfig):
|
|
|
138
139
|
:param token: the token to verify
|
|
139
140
|
"""
|
|
140
141
|
try:
|
|
141
|
-
decoded =
|
|
142
|
-
SESSION_ID.set(decoded.session_id)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
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)
|
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: str
|
|
42
|
+
identity_id: Optional[str] = None
|
|
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: str
|
|
59
|
+
identity_id: Optional[str] = None
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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:
|
|
48
|
+
raise AuthError(code=401, detail=EXPIRED_TOKEN_ERROR)
|
|
49
|
+
except jwt.DecodeError:
|
|
50
|
+
raise AuthError(code=401, detail=INVALID_TOKEN_ERROR)
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
def sign_jwt(
|
|
54
|
-
identity_id: str,
|
|
54
|
+
identity_id: Optional[str],
|
|
55
55
|
identity_name: str,
|
|
56
56
|
identity_email: Optional[str],
|
|
57
57
|
groups: List[str],
|
dara/core/base_definitions.py
CHANGED
|
@@ -21,16 +21,17 @@ 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
|
|
25
24
|
from enum import Enum
|
|
26
25
|
from typing import (
|
|
27
26
|
TYPE_CHECKING,
|
|
28
27
|
Annotated,
|
|
29
28
|
Any,
|
|
29
|
+
Awaitable,
|
|
30
30
|
Callable,
|
|
31
31
|
ClassVar,
|
|
32
32
|
Dict,
|
|
33
33
|
List,
|
|
34
|
+
Mapping,
|
|
34
35
|
Optional,
|
|
35
36
|
Tuple,
|
|
36
37
|
Union,
|
|
@@ -66,7 +67,7 @@ def annotation_has_base_model(typ: Any) -> bool:
|
|
|
66
67
|
type_args = get_args(typ)
|
|
67
68
|
if len(type_args) > 0:
|
|
68
69
|
return any(annotation_has_base_model(arg) for arg in type_args)
|
|
69
|
-
except:
|
|
70
|
+
except: # pylint: disable=bare-except
|
|
70
71
|
# canot get arguments, should be a simple type
|
|
71
72
|
pass
|
|
72
73
|
|
|
@@ -78,7 +79,9 @@ def annotation_has_base_model(typ: Any) -> bool:
|
|
|
78
79
|
# It works by adding SerializeAsAny to all fields of the model.
|
|
79
80
|
# See https://github.com/pydantic/pydantic/issues/6381
|
|
80
81
|
class SerializeAsAnyMeta(ModelMetaclass):
|
|
81
|
-
def __new__(
|
|
82
|
+
def __new__(
|
|
83
|
+
self, name: str, bases: Tuple[type], namespaces: Dict[str, Any], **kwargs
|
|
84
|
+
): # pylint: disable=bad-mcs-classmethod-argument
|
|
82
85
|
annotations: dict = namespaces.get('__annotations__', {}).copy()
|
|
83
86
|
|
|
84
87
|
for base in bases:
|
|
@@ -94,11 +97,11 @@ class SerializeAsAnyMeta(ModelMetaclass):
|
|
|
94
97
|
if isinstance(annotation, str) or annotation is ClassVar:
|
|
95
98
|
continue
|
|
96
99
|
if annotation_has_base_model(annotation):
|
|
97
|
-
annotations[field] = SerializeAsAny[annotation]
|
|
100
|
+
annotations[field] = SerializeAsAny[annotation] # type: ignore
|
|
98
101
|
|
|
99
102
|
namespaces['__annotations__'] = annotations
|
|
100
103
|
|
|
101
|
-
return super().__new__(
|
|
104
|
+
return super().__new__(self, name, bases, namespaces, **kwargs)
|
|
102
105
|
|
|
103
106
|
|
|
104
107
|
class DaraBaseModel(BaseModel, metaclass=SerializeAsAnyMeta):
|
|
@@ -132,17 +135,16 @@ class DaraBaseModel(BaseModel, metaclass=SerializeAsAnyMeta):
|
|
|
132
135
|
and annotation_has_base_model(field_info.annotation)
|
|
133
136
|
):
|
|
134
137
|
# Skip if it has metadata that is already annotated with SerializeAsAny
|
|
135
|
-
if any(isinstance(x, SerializeAsAny) for x in field_info.metadata):
|
|
138
|
+
if any(isinstance(x, SerializeAsAny) for x in field_info.metadata): # type: ignore
|
|
136
139
|
continue
|
|
137
140
|
# Skip if the type is already annotated with SerializeAsAny
|
|
138
141
|
if get_origin(field_info.annotation) is Annotated and any(
|
|
139
|
-
isinstance(arg, SerializeAsAny) #
|
|
140
|
-
for arg in field_info.annotation.__metadata__ # type: ignore
|
|
142
|
+
isinstance(arg, SerializeAsAny) for arg in field_info.annotation.__metadata__ # type: ignore
|
|
141
143
|
):
|
|
142
144
|
continue
|
|
143
145
|
|
|
144
|
-
field_info.annotation = SerializeAsAny[field_info.annotation]
|
|
145
|
-
field_info.metadata = list(field_info.annotation.__metadata__)
|
|
146
|
+
field_info.annotation = SerializeAsAny[field_info.annotation] # type: ignore
|
|
147
|
+
field_info.metadata = list(field_info.annotation.__metadata__) # type: ignore
|
|
146
148
|
|
|
147
149
|
# Rebuild again with force to ensure we rebuild the schema with new annotations
|
|
148
150
|
return super().model_rebuild(
|
|
@@ -253,8 +255,10 @@ class Cache:
|
|
|
253
255
|
if isinstance(arg, Cache.Type):
|
|
254
256
|
return LruCachePolicy(cache_type=arg)
|
|
255
257
|
|
|
256
|
-
if isinstance(arg, str)
|
|
257
|
-
|
|
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)
|
|
258
262
|
|
|
259
263
|
raise ValueError(
|
|
260
264
|
f'Invalid cache argument: {arg}. Please provide a Cache.Policy object or one of Cache.Type members'
|
|
@@ -304,12 +308,6 @@ class CachedRegistryEntry(BaseModel):
|
|
|
304
308
|
return f'{self.__class__.__name__}(cache={self.cache}, uid={self.uid})'
|
|
305
309
|
|
|
306
310
|
|
|
307
|
-
class NonTabularDataError(Exception):
|
|
308
|
-
"""
|
|
309
|
-
Raised when trying to interpret a non-tabular variable as tabular
|
|
310
|
-
"""
|
|
311
|
-
|
|
312
|
-
|
|
313
311
|
class BaseTaskMessage(BaseModel):
|
|
314
312
|
task_id: str
|
|
315
313
|
|
|
@@ -351,10 +349,12 @@ class BaseTask(abc.ABC):
|
|
|
351
349
|
super().__init__()
|
|
352
350
|
|
|
353
351
|
@abc.abstractmethod
|
|
354
|
-
async def run(self, send_stream: Optional[MemoryObjectSendStream[TaskMessage]] = None) -> Any:
|
|
352
|
+
async def run(self, send_stream: Optional[MemoryObjectSendStream[TaskMessage]] = None) -> Any:
|
|
353
|
+
...
|
|
355
354
|
|
|
356
355
|
@abc.abstractmethod
|
|
357
|
-
async def cancel(self):
|
|
356
|
+
async def cancel(self):
|
|
357
|
+
...
|
|
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 = Exception('Task was cancelled')
|
|
417
417
|
self.event.set()
|
|
418
418
|
|
|
419
419
|
def add_subscriber(self):
|
|
@@ -438,6 +438,47 @@ 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
|
+
|
|
441
482
|
class AnnotatedAction(BaseModel):
|
|
442
483
|
"""
|
|
443
484
|
Represents a single call to an @action-annotated action.
|
|
@@ -463,11 +504,12 @@ class AnnotatedAction(BaseModel):
|
|
|
463
504
|
dynamic_kwargs: Mapping[str, Any]
|
|
464
505
|
"""Dynamic kwargs of the action; uid -> variable instance"""
|
|
465
506
|
|
|
466
|
-
loading: Variable
|
|
507
|
+
loading: 'Variable' # type: ignore
|
|
467
508
|
"""Loading Variable instance"""
|
|
468
509
|
|
|
469
510
|
def __init__(self, **data):
|
|
470
511
|
# Resolve the circular dependency to add a loading Variable to the model upon creation
|
|
512
|
+
# pylint: disable-next=import-error, import-outside-toplevel
|
|
471
513
|
from dara.core.interactivity.plain_variable import Variable
|
|
472
514
|
|
|
473
515
|
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 uvicorn
|
|
25
|
-
|
|
26
24
|
import click
|
|
25
|
+
import uvicorn
|
|
27
26
|
from click.exceptions import UsageError
|
|
27
|
+
|
|
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,11 +214,10 @@ def dev():
|
|
|
214
214
|
stderr=subprocess.STDOUT,
|
|
215
215
|
shell=True,
|
|
216
216
|
) as vite_process:
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
print(decoded_line)
|
|
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
|
|
222
221
|
|
|
223
222
|
|
|
224
223
|
@cli.command()
|
dara/core/configuration.py
CHANGED
|
@@ -14,7 +14,6 @@ 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
|
-
|
|
18
17
|
import os
|
|
19
18
|
import pathlib
|
|
20
19
|
from inspect import isclass, isfunction
|
|
@@ -387,7 +386,7 @@ class ConfigurationBuilder:
|
|
|
387
386
|
icon: Optional[str] = None,
|
|
388
387
|
route: Optional[str] = None,
|
|
389
388
|
include_in_menu: Optional[bool] = True,
|
|
390
|
-
reset_vars_on_load: Optional[List[AnyVariable]] =
|
|
389
|
+
reset_vars_on_load: Optional[List[AnyVariable]] = [],
|
|
391
390
|
on_load: Optional[Action] = None,
|
|
392
391
|
):
|
|
393
392
|
"""
|
|
@@ -403,8 +402,6 @@ class ConfigurationBuilder:
|
|
|
403
402
|
:param on_load: optional action to execute upon visiting the page
|
|
404
403
|
"""
|
|
405
404
|
# Backwards compatibility - deprecated
|
|
406
|
-
if reset_vars_on_load is None:
|
|
407
|
-
reset_vars_on_load = []
|
|
408
405
|
if reset_vars_on_load is not None and len(reset_vars_on_load) > 0:
|
|
409
406
|
if on_load is not None:
|
|
410
407
|
raise ValueError('reset_vars_on_load and on_load cannot be used together')
|
|
@@ -488,7 +485,7 @@ class ConfigurationBuilder:
|
|
|
488
485
|
if len(options) > 0:
|
|
489
486
|
dev_logger.warning(f'Options provided for a function middleware {middleware}, but they will be ignored')
|
|
490
487
|
elif isclass(middleware):
|
|
491
|
-
constructed_middleware = Middleware(middleware, **options)
|
|
488
|
+
constructed_middleware = Middleware(middleware, **options) # type: ignore
|
|
492
489
|
else:
|
|
493
490
|
raise ValueError(f'Invalid middleware type: {type(middleware)}')
|
|
494
491
|
|
dara/core/css.py
CHANGED
|
@@ -18,7 +18,8 @@ limitations under the License.
|
|
|
18
18
|
from typing import Literal, Optional
|
|
19
19
|
|
|
20
20
|
# Re-export CSSProperties for easier importing
|
|
21
|
-
|
|
21
|
+
# pylint: disable=unused-import
|
|
22
|
+
from dara.core.visual.css import CSSProperties
|
|
22
23
|
|
|
23
24
|
IconStyle = Literal['solid', 'regular', 'brands']
|
|
24
25
|
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,9 +25,10 @@ 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
|
+
DerivedDataVariable,
|
|
29
29
|
DerivedVariable,
|
|
30
30
|
DownloadContent,
|
|
31
|
+
NonDataVariable,
|
|
31
32
|
SideEffect,
|
|
32
33
|
Variable,
|
|
33
34
|
)
|
|
@@ -115,7 +116,7 @@ class FileStore(BaseModel):
|
|
|
115
116
|
if not os.path.exists(file_path):
|
|
116
117
|
return None
|
|
117
118
|
|
|
118
|
-
return open(file_path, 'rb')
|
|
119
|
+
return io.open(file_path, 'rb')
|
|
119
120
|
|
|
120
121
|
def write_file(self, cache_type: CacheType, name: str) -> io.BufferedWriter:
|
|
121
122
|
"""
|
|
@@ -129,7 +130,7 @@ class FileStore(BaseModel):
|
|
|
129
130
|
"""
|
|
130
131
|
scope_path = self.get_scoped_path(cache_type)
|
|
131
132
|
os.makedirs(scope_path, exist_ok=True)
|
|
132
|
-
return open(os.path.join(scope_path, name), 'wb')
|
|
133
|
+
return io.open(os.path.join(scope_path, name), 'wb')
|
|
133
134
|
|
|
134
135
|
def delete_file(self, cache_type: CacheType, name: str) -> None:
|
|
135
136
|
"""
|
|
@@ -171,7 +172,7 @@ class DataFactory(BaseModel):
|
|
|
171
172
|
|
|
172
173
|
def list_datasets_var(
|
|
173
174
|
self,
|
|
174
|
-
cache: Union[CacheType,
|
|
175
|
+
cache: Union[CacheType, NonDataVariable] = CacheType.GLOBAL,
|
|
175
176
|
polling_interval: Optional[int] = None,
|
|
176
177
|
) -> DerivedVariable[List[str]]:
|
|
177
178
|
"""
|
|
@@ -180,7 +181,7 @@ class DataFactory(BaseModel):
|
|
|
180
181
|
:param cache: cache type to get the list of datasets for
|
|
181
182
|
:param polling_interval: optional polling_interval in seconds for the derived variable
|
|
182
183
|
"""
|
|
183
|
-
cache_var = cache if isinstance(cache,
|
|
184
|
+
cache_var = cache if isinstance(cache, NonDataVariable) else Variable(cache)
|
|
184
185
|
|
|
185
186
|
return DerivedVariable(
|
|
186
187
|
lambda cache_val: self.file_store.list_files(cache_val or CacheType.GLOBAL),
|
|
@@ -288,21 +289,21 @@ class DataFactory(BaseModel):
|
|
|
288
289
|
|
|
289
290
|
def read_dataset_var(
|
|
290
291
|
self,
|
|
291
|
-
name: Union[str,
|
|
292
|
-
cache: Union[CacheType,
|
|
292
|
+
name: Union[str, NonDataVariable],
|
|
293
|
+
cache: Union[CacheType, NonDataVariable] = CacheType.GLOBAL,
|
|
293
294
|
polling_interval: Optional[int] = None,
|
|
294
|
-
) ->
|
|
295
|
+
) -> DerivedDataVariable:
|
|
295
296
|
"""
|
|
296
|
-
Create a
|
|
297
|
+
Create a DerivedDataVariable which reads a specific dataset from disk
|
|
297
298
|
|
|
298
299
|
:param name: name of the dataset
|
|
299
300
|
:param cache: cache to get the dataset for
|
|
300
301
|
:param polling_interval: optional polling interval in seconds for the derived variable
|
|
301
302
|
"""
|
|
302
|
-
name_var = name if isinstance(name,
|
|
303
|
-
cache_var = cache if isinstance(cache,
|
|
303
|
+
name_var = name if isinstance(name, NonDataVariable) else Variable(name)
|
|
304
|
+
cache_var = cache if isinstance(cache, NonDataVariable) else Variable(cache)
|
|
304
305
|
|
|
305
|
-
return
|
|
306
|
+
return DerivedDataVariable(
|
|
306
307
|
self.read_dataset,
|
|
307
308
|
variables=[name_var, cache_var],
|
|
308
309
|
cache=CacheType.SESSION,
|
|
@@ -319,7 +320,7 @@ class DataFactory(BaseModel):
|
|
|
319
320
|
self.file_store.delete_file(cache, name)
|
|
320
321
|
|
|
321
322
|
def delete_dataset_action(
|
|
322
|
-
self, name: Union[str,
|
|
323
|
+
self, name: Union[str, NonDataVariable], cache: Union[CacheType, NonDataVariable] = CacheType.GLOBAL
|
|
323
324
|
):
|
|
324
325
|
"""
|
|
325
326
|
Get a SideEffect action which deletes a given dataset
|
|
@@ -327,13 +328,13 @@ class DataFactory(BaseModel):
|
|
|
327
328
|
:param name: name of the dataset
|
|
328
329
|
:param cache: cache to remove the daatset for
|
|
329
330
|
"""
|
|
330
|
-
name_var = name if isinstance(name,
|
|
331
|
-
cache_var = cache if isinstance(cache,
|
|
331
|
+
name_var = name if isinstance(name, NonDataVariable) else Variable(name)
|
|
332
|
+
cache_var = cache if isinstance(cache, NonDataVariable) else Variable(cache)
|
|
332
333
|
|
|
333
334
|
return SideEffect(lambda ctx: self.delete_dataset(ctx.extras[0], ctx.extras[1]), extras=[name_var, cache_var])
|
|
334
335
|
|
|
335
336
|
def download_dataset_action(
|
|
336
|
-
self, name: Union[str,
|
|
337
|
+
self, name: Union[str, NonDataVariable], cache: Union[CacheType, NonDataVariable] = CacheType.GLOBAL
|
|
337
338
|
):
|
|
338
339
|
"""
|
|
339
340
|
Get a DownloadContent action which downloads a dataset with a given name as a .csv
|
|
@@ -341,8 +342,8 @@ class DataFactory(BaseModel):
|
|
|
341
342
|
:param name: name of the dataset to download
|
|
342
343
|
:param cache: cache to download dataset for
|
|
343
344
|
"""
|
|
344
|
-
name_var = name if isinstance(name,
|
|
345
|
-
cache_var = cache if isinstance(cache,
|
|
345
|
+
name_var = name if isinstance(name, NonDataVariable) else Variable(name)
|
|
346
|
+
cache_var = cache if isinstance(cache, NonDataVariable) else Variable(cache)
|
|
346
347
|
|
|
347
348
|
def _resolver(ctx: DownloadContent.Ctx): # type: ignore
|
|
348
349
|
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
|
|
21
21
|
|
|
22
22
|
from dara.core.base_definitions import ActionDef
|
|
23
23
|
from dara.core.interactivity.actions import (
|
|
@@ -53,7 +53,6 @@ from dara.core.visual.components import (
|
|
|
53
53
|
TopBarFrame,
|
|
54
54
|
TopBarFrameDef,
|
|
55
55
|
)
|
|
56
|
-
from dara.core.visual.components.fallback import CustomFallbackDef
|
|
57
56
|
from dara.core.visual.template import TemplateBuilder
|
|
58
57
|
|
|
59
58
|
if TYPE_CHECKING:
|
|
@@ -78,9 +77,8 @@ CORE_COMPONENTS: Dict[str, ComponentTypeAnnotation] = {
|
|
|
78
77
|
RouterContent.__name__: RouterContentDef,
|
|
79
78
|
SideBarFrame.__name__: SideBarFrameDef,
|
|
80
79
|
TopBarFrame.__name__: TopBarFrameDef,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
cast(str, Fallback.Custom.py_component): CustomFallbackDef,
|
|
80
|
+
Fallback.Default.py_component: DefaultFallbackDef,
|
|
81
|
+
Fallback.Row.py_component: RowFallbackDef,
|
|
84
82
|
For.__name__: ForDef,
|
|
85
83
|
}
|
|
86
84
|
|
|
@@ -95,9 +93,9 @@ CORE_ACTIONS: Dict[str, ActionDef] = {
|
|
|
95
93
|
Notify.__name__: NotifyDef,
|
|
96
94
|
}
|
|
97
95
|
|
|
98
|
-
|
|
99
96
|
# Define a default layout template
|
|
100
97
|
def default_template(config: Configuration) -> Template:
|
|
98
|
+
|
|
101
99
|
template = TemplateBuilder(name='default')
|
|
102
100
|
|
|
103
101
|
router = template.add_router_from_pages(list(config.pages.values()))
|
|
@@ -109,6 +107,7 @@ def default_template(config: Configuration) -> Template:
|
|
|
109
107
|
|
|
110
108
|
# Define a blank template
|
|
111
109
|
def blank_template(config: Configuration) -> Template:
|
|
110
|
+
|
|
112
111
|
template = TemplateBuilder(name='default')
|
|
113
112
|
|
|
114
113
|
router = template.add_router_from_pages(list(config.pages.values()))
|
|
@@ -120,6 +119,7 @@ def blank_template(config: Configuration) -> Template:
|
|
|
120
119
|
|
|
121
120
|
# Define a top layout template
|
|
122
121
|
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,6 +131,7 @@ 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
|
+
|
|
134
135
|
template = TemplateBuilder(name='default')
|
|
135
136
|
|
|
136
137
|
router = template.add_router_from_pages(list(config.pages.values()))
|