dara-core 1.21.15__py3-none-any.whl → 1.21.17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dara/core/auth/base.py +5 -5
- dara/core/auth/basic.py +3 -3
- dara/core/auth/definitions.py +13 -14
- dara/core/auth/routes.py +7 -5
- dara/core/auth/utils.py +11 -10
- dara/core/base_definitions.py +30 -36
- dara/core/cli.py +7 -8
- dara/core/configuration.py +51 -58
- dara/core/css.py +2 -2
- dara/core/data_utils.py +12 -17
- dara/core/defaults.py +3 -3
- dara/core/definitions.py +58 -63
- dara/core/http.py +4 -4
- dara/core/interactivity/actions.py +34 -42
- dara/core/interactivity/any_data_variable.py +1 -1
- dara/core/interactivity/any_variable.py +6 -5
- dara/core/interactivity/client_variable.py +1 -2
- dara/core/interactivity/condition.py +2 -2
- dara/core/interactivity/data_variable.py +2 -4
- dara/core/interactivity/derived_data_variable.py +7 -10
- dara/core/interactivity/derived_variable.py +45 -51
- dara/core/interactivity/filtering.py +19 -19
- dara/core/interactivity/loop_variable.py +2 -4
- dara/core/interactivity/non_data_variable.py +1 -1
- dara/core/interactivity/plain_variable.py +21 -18
- dara/core/interactivity/server_variable.py +13 -15
- dara/core/interactivity/state_variable.py +4 -5
- dara/core/interactivity/switch_variable.py +16 -16
- dara/core/interactivity/tabular_variable.py +3 -3
- dara/core/interactivity/url_variable.py +3 -3
- dara/core/internal/cache_store/cache_store.py +6 -6
- dara/core/internal/cache_store/keep_all.py +3 -3
- dara/core/internal/cache_store/lru.py +8 -8
- dara/core/internal/cache_store/ttl.py +4 -4
- dara/core/internal/custom_response.py +3 -3
- dara/core/internal/dependency_resolution.py +6 -10
- dara/core/internal/devtools.py +2 -3
- dara/core/internal/download.py +5 -6
- dara/core/internal/encoder_registry.py +7 -11
- dara/core/internal/execute_action.py +5 -5
- dara/core/internal/hashing.py +1 -2
- dara/core/internal/import_discovery.py +7 -9
- dara/core/internal/normalization.py +12 -15
- dara/core/internal/pandas_utils.py +6 -6
- dara/core/internal/pool/channel.py +3 -4
- dara/core/internal/pool/definitions.py +9 -9
- dara/core/internal/pool/task_pool.py +8 -8
- dara/core/internal/pool/utils.py +4 -3
- dara/core/internal/pool/worker.py +3 -3
- dara/core/internal/registries.py +4 -4
- dara/core/internal/registry.py +3 -3
- dara/core/internal/registry_lookup.py +4 -4
- dara/core/internal/routing.py +23 -22
- dara/core/internal/scheduler.py +8 -8
- dara/core/internal/settings.py +1 -2
- dara/core/internal/store.py +9 -9
- dara/core/internal/tasks.py +30 -30
- dara/core/internal/utils.py +9 -15
- dara/core/internal/websocket.py +18 -18
- dara/core/js_tooling/js_utils.py +19 -19
- dara/core/logging.py +13 -13
- dara/core/main.py +4 -5
- dara/core/metrics/cache.py +2 -4
- dara/core/persistence.py +19 -25
- dara/core/router/compat.py +1 -3
- dara/core/router/components.py +10 -10
- dara/core/router/dependency_graph.py +2 -4
- dara/core/router/router.py +43 -42
- dara/core/visual/components/dynamic_component.py +1 -3
- dara/core/visual/components/fallback.py +3 -3
- dara/core/visual/components/for_cmp.py +5 -5
- dara/core/visual/components/menu.py +1 -3
- dara/core/visual/components/router_content.py +1 -3
- dara/core/visual/components/sidebar_frame.py +8 -10
- dara/core/visual/components/theme_provider.py +3 -3
- dara/core/visual/components/topbar_frame.py +8 -10
- dara/core/visual/css/__init__.py +277 -277
- dara/core/visual/dynamic_component.py +18 -22
- dara/core/visual/progress_updater.py +1 -1
- dara/core/visual/template.py +10 -12
- dara/core/visual/themes/definitions.py +46 -46
- {dara_core-1.21.15.dist-info → dara_core-1.21.17.dist-info}/METADATA +13 -14
- dara_core-1.21.17.dist-info/RECORD +127 -0
- dara_core-1.21.15.dist-info/RECORD +0 -127
- {dara_core-1.21.15.dist-info → dara_core-1.21.17.dist-info}/LICENSE +0 -0
- {dara_core-1.21.15.dist-info → dara_core-1.21.17.dist-info}/WHEEL +0 -0
- {dara_core-1.21.15.dist-info → dara_core-1.21.17.dist-info}/entry_points.txt +0 -0
dara/core/internal/websocket.py
CHANGED
|
@@ -20,7 +20,7 @@ import inspect
|
|
|
20
20
|
import math
|
|
21
21
|
import uuid
|
|
22
22
|
from contextvars import ContextVar
|
|
23
|
-
from typing import Any,
|
|
23
|
+
from typing import Any, Literal
|
|
24
24
|
from uuid import uuid4
|
|
25
25
|
|
|
26
26
|
import anyio
|
|
@@ -56,14 +56,14 @@ class DaraClientMessage(BaseModel):
|
|
|
56
56
|
|
|
57
57
|
type: Literal['message'] = 'message'
|
|
58
58
|
channel: str
|
|
59
|
-
chunk_count:
|
|
59
|
+
chunk_count: int | None = None
|
|
60
60
|
message: Any
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
class CustomClientMessagePayload(BaseModel):
|
|
64
64
|
model_config = ConfigDict(serialize_by_alias=True)
|
|
65
65
|
|
|
66
|
-
rchan:
|
|
66
|
+
rchan: str | None = Field(default=None, alias='__rchan')
|
|
67
67
|
"""Return channel if the message is expected to have a response for"""
|
|
68
68
|
|
|
69
69
|
kind: str
|
|
@@ -89,17 +89,17 @@ class CustomClientMessage(BaseModel):
|
|
|
89
89
|
message: CustomClientMessagePayload
|
|
90
90
|
|
|
91
91
|
|
|
92
|
-
ClientMessage =
|
|
92
|
+
ClientMessage = DaraClientMessage | CustomClientMessage
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
# Server message types
|
|
96
96
|
class ServerMessagePayload(BaseModel):
|
|
97
97
|
model_config = ConfigDict(serialize_by_alias=True, extra='allow')
|
|
98
98
|
|
|
99
|
-
rchan:
|
|
99
|
+
rchan: str | None = Field(default=None, alias='__rchan')
|
|
100
100
|
"""Return channel if the message is expected to have a response for"""
|
|
101
101
|
|
|
102
|
-
response_for:
|
|
102
|
+
response_for: str | None = Field(default=None, alias='__response_for')
|
|
103
103
|
"""ID of the __rchan included in the original client message if this message is a response to a client message"""
|
|
104
104
|
|
|
105
105
|
@model_serializer(mode='wrap')
|
|
@@ -140,11 +140,11 @@ class CustomServerMessage(BaseModel):
|
|
|
140
140
|
message: CustomServerMessagePayload
|
|
141
141
|
|
|
142
142
|
|
|
143
|
-
ServerPayload =
|
|
144
|
-
LoosePayload =
|
|
145
|
-
ServerMessage =
|
|
143
|
+
ServerPayload = ServerMessagePayload | CustomServerMessagePayload
|
|
144
|
+
LoosePayload = ServerPayload | dict
|
|
145
|
+
ServerMessage = DaraServerMessage | CustomServerMessage
|
|
146
146
|
|
|
147
|
-
WS_CHANNEL: ContextVar[
|
|
147
|
+
WS_CHANNEL: ContextVar[str | None] = ContextVar('ws_channel', default=None)
|
|
148
148
|
|
|
149
149
|
|
|
150
150
|
class WebSocketHandler:
|
|
@@ -167,7 +167,7 @@ class WebSocketHandler:
|
|
|
167
167
|
Stream containing messages to send to the client.
|
|
168
168
|
"""
|
|
169
169
|
|
|
170
|
-
pending_responses:
|
|
170
|
+
pending_responses: dict[str, tuple[Event, Any | None]]
|
|
171
171
|
"""
|
|
172
172
|
A map of pending responses from the client. The key is the message ID and the value is a tuple of the event to
|
|
173
173
|
notify when the response is received and the response data.
|
|
@@ -276,7 +276,7 @@ class WebSocketHandler:
|
|
|
276
276
|
# unreachable but needed for pylint to be happy
|
|
277
277
|
return None
|
|
278
278
|
|
|
279
|
-
async def send_and_wait(self, message: ServerMessage) ->
|
|
279
|
+
async def send_and_wait(self, message: ServerMessage) -> Any | None:
|
|
280
280
|
"""
|
|
281
281
|
Send a message to the client and return the client's response
|
|
282
282
|
|
|
@@ -300,7 +300,7 @@ class WebSocketHandler:
|
|
|
300
300
|
return response_data
|
|
301
301
|
|
|
302
302
|
|
|
303
|
-
def get_user_channels(user_identifier: str) ->
|
|
303
|
+
def get_user_channels(user_identifier: str) -> set[str]:
|
|
304
304
|
"""
|
|
305
305
|
Get connected websocket channels associated with a given user.
|
|
306
306
|
|
|
@@ -311,7 +311,7 @@ def get_user_channels(user_identifier: str) -> Set[str]:
|
|
|
311
311
|
if sessions_registry.has(user_identifier):
|
|
312
312
|
user_sessions = sessions_registry.get(user_identifier)
|
|
313
313
|
|
|
314
|
-
channels:
|
|
314
|
+
channels: set[str] = set()
|
|
315
315
|
for session_id in user_sessions:
|
|
316
316
|
if websocket_registry.has(session_id):
|
|
317
317
|
channels |= websocket_registry.get(session_id)
|
|
@@ -327,7 +327,7 @@ class WebsocketManager:
|
|
|
327
327
|
"""
|
|
328
328
|
|
|
329
329
|
def __init__(self):
|
|
330
|
-
self.handlers:
|
|
330
|
+
self.handlers: dict[str, WebSocketHandler] = {}
|
|
331
331
|
"""
|
|
332
332
|
A mapping of channel IDs to WebSocketHandler instances.
|
|
333
333
|
"""
|
|
@@ -354,7 +354,7 @@ class WebsocketManager:
|
|
|
354
354
|
self.handlers[channel_id] = handler
|
|
355
355
|
return handler
|
|
356
356
|
|
|
357
|
-
async def broadcast(self, message: LoosePayload, custom=False, ignore_channel:
|
|
357
|
+
async def broadcast(self, message: LoosePayload, custom=False, ignore_channel: str | None = None):
|
|
358
358
|
"""
|
|
359
359
|
Send a message to all connected clients.
|
|
360
360
|
|
|
@@ -369,7 +369,7 @@ class WebsocketManager:
|
|
|
369
369
|
tg.start_soon(handler.send_message, self._construct_message(message, custom))
|
|
370
370
|
|
|
371
371
|
async def send_message_to_user(
|
|
372
|
-
self, user_id: str, message: LoosePayload, custom=False, ignore_channel:
|
|
372
|
+
self, user_id: str, message: LoosePayload, custom=False, ignore_channel: str | None = None
|
|
373
373
|
):
|
|
374
374
|
"""
|
|
375
375
|
Send a message to all connected channels associated with the given user.
|
|
@@ -424,7 +424,7 @@ class WebsocketManager:
|
|
|
424
424
|
del self.handlers[channel_id]
|
|
425
425
|
|
|
426
426
|
|
|
427
|
-
async def ws_handler(websocket: WebSocket, token:
|
|
427
|
+
async def ws_handler(websocket: WebSocket, token: str | None = Query(default=None)):
|
|
428
428
|
"""
|
|
429
429
|
Websocket handler. Used for live_reloading in dev mode and for notifying the UI of task results.
|
|
430
430
|
|
dara/core/js_tooling/js_utils.py
CHANGED
|
@@ -24,7 +24,7 @@ import shutil
|
|
|
24
24
|
import sys
|
|
25
25
|
from enum import Enum
|
|
26
26
|
from importlib.metadata import version
|
|
27
|
-
from typing import Any, ClassVar,
|
|
27
|
+
from typing import Any, ClassVar, Literal, Optional, Union, cast
|
|
28
28
|
|
|
29
29
|
from packaging.version import Version
|
|
30
30
|
from pydantic import BaseModel
|
|
@@ -47,7 +47,7 @@ class JsConfig(BaseModel):
|
|
|
47
47
|
local_entry: str
|
|
48
48
|
"""Relative path to the local entrypoint from the package root"""
|
|
49
49
|
|
|
50
|
-
extra_dependencies:
|
|
50
|
+
extra_dependencies: dict[str, str]
|
|
51
51
|
"""Extra dependencies to add to package.json before running install"""
|
|
52
52
|
|
|
53
53
|
package_manager: Literal['npm', 'yarn', 'pnpm']
|
|
@@ -77,13 +77,13 @@ class BuildConfig(BaseModel):
|
|
|
77
77
|
dev: bool
|
|
78
78
|
"""Whether dev (HMR) mode is enabled"""
|
|
79
79
|
|
|
80
|
-
js_config:
|
|
80
|
+
js_config: JsConfig | None = None
|
|
81
81
|
"""Custom JS configuration from dara.config.json file"""
|
|
82
82
|
|
|
83
|
-
npm_registry:
|
|
83
|
+
npm_registry: str | None = None
|
|
84
84
|
"""Optional npm registry url to pull packages from"""
|
|
85
85
|
|
|
86
|
-
npm_token:
|
|
86
|
+
npm_token: str | None = None
|
|
87
87
|
"""Optional npm token for the registry url added above"""
|
|
88
88
|
|
|
89
89
|
@staticmethod
|
|
@@ -109,7 +109,7 @@ class BuildCacheDiff(BaseModel):
|
|
|
109
109
|
Contains a list of keys that have changed.
|
|
110
110
|
"""
|
|
111
111
|
|
|
112
|
-
keys:
|
|
112
|
+
keys: set[BuildCacheKey]
|
|
113
113
|
|
|
114
114
|
def should_rebuild_js(self) -> bool:
|
|
115
115
|
"""
|
|
@@ -119,7 +119,7 @@ class BuildCacheDiff(BaseModel):
|
|
|
119
119
|
- package_map changed - required packages changed, we need to install new packages
|
|
120
120
|
- build_config changed - build config changed, we need to install with a different config
|
|
121
121
|
"""
|
|
122
|
-
full_rebuild_keys:
|
|
122
|
+
full_rebuild_keys: set[BuildCacheKey] = set(['static_files_dir', 'package_map', 'build_config'])
|
|
123
123
|
|
|
124
124
|
return len(full_rebuild_keys.intersection(self.keys)) > 0
|
|
125
125
|
|
|
@@ -137,13 +137,13 @@ class BuildCache(BaseModel):
|
|
|
137
137
|
how to handle the frontend assets and statics.
|
|
138
138
|
"""
|
|
139
139
|
|
|
140
|
-
static_folders:
|
|
140
|
+
static_folders: list[str]
|
|
141
141
|
"""List of static folders registered"""
|
|
142
142
|
|
|
143
143
|
static_files_dir: str
|
|
144
144
|
"""Static files output folder"""
|
|
145
145
|
|
|
146
|
-
package_map:
|
|
146
|
+
package_map: dict[str, str]
|
|
147
147
|
"""Map of py_module_name to js_module_name"""
|
|
148
148
|
|
|
149
149
|
build_config: BuildConfig
|
|
@@ -152,7 +152,7 @@ class BuildCache(BaseModel):
|
|
|
152
152
|
FILENAME: ClassVar[str] = '_build.json'
|
|
153
153
|
|
|
154
154
|
@staticmethod
|
|
155
|
-
def from_config(config: Configuration, build_config:
|
|
155
|
+
def from_config(config: Configuration, build_config: BuildConfig | None = None):
|
|
156
156
|
"""
|
|
157
157
|
Create a BuildCache from a Configuration
|
|
158
158
|
|
|
@@ -202,7 +202,7 @@ class BuildCache(BaseModel):
|
|
|
202
202
|
# Otherwise copy file if doesn't already exist
|
|
203
203
|
shutil.copy2(file_or_dir_path, self.static_files_dir)
|
|
204
204
|
|
|
205
|
-
def find_favicon(self) ->
|
|
205
|
+
def find_favicon(self) -> str | None:
|
|
206
206
|
"""
|
|
207
207
|
Find the favicon in the static files directories, looks for any .ico file
|
|
208
208
|
|
|
@@ -250,7 +250,7 @@ class BuildCache(BaseModel):
|
|
|
250
250
|
os.unlink(new_node_modules_path)
|
|
251
251
|
os.symlink(node_modules_path, new_node_modules_path)
|
|
252
252
|
|
|
253
|
-
def get_importers(self) ->
|
|
253
|
+
def get_importers(self) -> dict[str, str]:
|
|
254
254
|
"""
|
|
255
255
|
Get the importers map for this BuildCache.
|
|
256
256
|
Includes `self.package_map` and a local entry if it exists.
|
|
@@ -265,7 +265,7 @@ class BuildCache(BaseModel):
|
|
|
265
265
|
|
|
266
266
|
return importers
|
|
267
267
|
|
|
268
|
-
def get_py_modules(self) ->
|
|
268
|
+
def get_py_modules(self) -> list[str]:
|
|
269
269
|
"""
|
|
270
270
|
Get a list of all py modules used in this BuildCache
|
|
271
271
|
"""
|
|
@@ -279,7 +279,7 @@ class BuildCache(BaseModel):
|
|
|
279
279
|
|
|
280
280
|
return list(py_modules)
|
|
281
281
|
|
|
282
|
-
def get_package_json(self) ->
|
|
282
|
+
def get_package_json(self) -> dict[str, Any]:
|
|
283
283
|
"""
|
|
284
284
|
Generate a package.json file for this BuildCache
|
|
285
285
|
"""
|
|
@@ -320,7 +320,7 @@ class BuildCache(BaseModel):
|
|
|
320
320
|
|
|
321
321
|
return pkg_json
|
|
322
322
|
|
|
323
|
-
def get_diff(self, other:
|
|
323
|
+
def get_diff(self, other: Union['BuildCache', str] | None = None) -> BuildCacheDiff:
|
|
324
324
|
"""
|
|
325
325
|
Get the diff between this BuildCache and another.
|
|
326
326
|
Returns a list of keys that have changed.
|
|
@@ -339,7 +339,7 @@ class BuildCache(BaseModel):
|
|
|
339
339
|
else:
|
|
340
340
|
other_cache = other
|
|
341
341
|
|
|
342
|
-
diff:
|
|
342
|
+
diff: set[BuildCacheKey] = set()
|
|
343
343
|
|
|
344
344
|
if set(self.static_folders) != set(other_cache.static_folders):
|
|
345
345
|
diff.add('static_folders')
|
|
@@ -439,7 +439,7 @@ def _get_module_file(module: str) -> str:
|
|
|
439
439
|
return cast(str, imported_module.__file__)
|
|
440
440
|
|
|
441
441
|
|
|
442
|
-
def rebuild_js(build_cache: BuildCache, build_diff:
|
|
442
|
+
def rebuild_js(build_cache: BuildCache, build_diff: BuildCacheDiff | None = None):
|
|
443
443
|
"""
|
|
444
444
|
Generic 'rebuild' function which bundles/prepares assets depending on the build mode chosen
|
|
445
445
|
|
|
@@ -641,7 +641,7 @@ def prepare_autojs_assets(build_cache: BuildCache):
|
|
|
641
641
|
shutil.copy(css_asset_path, os.path.join(build_cache.static_files_dir, f'{module_name}.css'))
|
|
642
642
|
|
|
643
643
|
|
|
644
|
-
def build_autojs_template(build_cache: BuildCache, config: Configuration) ->
|
|
644
|
+
def build_autojs_template(build_cache: BuildCache, config: Configuration) -> dict[str, Any]:
|
|
645
645
|
"""
|
|
646
646
|
Build the autojs html template by preparing context data with required tags based on packages loaded
|
|
647
647
|
and including the startup script
|
|
@@ -669,7 +669,7 @@ def build_autojs_template(build_cache: BuildCache, config: Configuration) -> Dic
|
|
|
669
669
|
|
|
670
670
|
core_version = _get_py_version('dara.core')
|
|
671
671
|
|
|
672
|
-
package_tags:
|
|
672
|
+
package_tags: dict[str, list[str]] = {
|
|
673
673
|
'dara.core': [
|
|
674
674
|
f'<script crossorigin src="{settings.dara_base_url}/static/dara.core.umd.js?v={core_version}"></script>',
|
|
675
675
|
f'<link rel="stylesheet" href="{settings.dara_base_url}/static/dara.core.css?v={core_version}"></link>',
|
dara/core/logging.py
CHANGED
|
@@ -20,7 +20,7 @@ import logging
|
|
|
20
20
|
import sys
|
|
21
21
|
import time
|
|
22
22
|
import traceback
|
|
23
|
-
from typing import Any
|
|
23
|
+
from typing import Any
|
|
24
24
|
|
|
25
25
|
import colorama
|
|
26
26
|
from colorama import Back, Fore
|
|
@@ -31,7 +31,7 @@ from starlette.types import Message
|
|
|
31
31
|
|
|
32
32
|
colorama.init()
|
|
33
33
|
|
|
34
|
-
JsonSerializable =
|
|
34
|
+
JsonSerializable = str | int | float | dict | None
|
|
35
35
|
|
|
36
36
|
one_mb = int(1024 * 1024)
|
|
37
37
|
|
|
@@ -49,32 +49,32 @@ class Logger:
|
|
|
49
49
|
"""
|
|
50
50
|
return self._logger.getEffectiveLevel()
|
|
51
51
|
|
|
52
|
-
def info(self, title: str, extra:
|
|
52
|
+
def info(self, title: str, extra: dict[str, Any] | None = None):
|
|
53
53
|
"""
|
|
54
54
|
Log a message at the INFO level
|
|
55
55
|
|
|
56
56
|
:param title: short title for the log message
|
|
57
57
|
:param extra: an optional field for any extra info to be passed along
|
|
58
58
|
"""
|
|
59
|
-
payload:
|
|
59
|
+
payload: dict[str, JsonSerializable] = {
|
|
60
60
|
'title': title,
|
|
61
61
|
}
|
|
62
62
|
self._logger.info(payload, extra={'content': extra})
|
|
63
63
|
|
|
64
|
-
def warning(self, title: str, extra:
|
|
64
|
+
def warning(self, title: str, extra: dict[str, Any] | None = None):
|
|
65
65
|
"""
|
|
66
66
|
Log a message at the WARNING level
|
|
67
67
|
|
|
68
68
|
:param title: short title for the log message
|
|
69
69
|
:param extra: an optional field for any extra info to be passed along
|
|
70
70
|
"""
|
|
71
|
-
payload:
|
|
71
|
+
payload: dict[str, JsonSerializable] = {
|
|
72
72
|
'title': title,
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
self._logger.warning(payload, extra={'content': extra})
|
|
76
76
|
|
|
77
|
-
def error(self, title: str, error: BaseException, extra:
|
|
77
|
+
def error(self, title: str, error: BaseException, extra: dict[str, Any] | None = None):
|
|
78
78
|
"""
|
|
79
79
|
Log a message at the ERROR level
|
|
80
80
|
|
|
@@ -86,7 +86,7 @@ class Logger:
|
|
|
86
86
|
|
|
87
87
|
self._logger.error(payload, extra={'content': extra})
|
|
88
88
|
|
|
89
|
-
def debug(self, title: str, description:
|
|
89
|
+
def debug(self, title: str, description: str | None = None, extra: dict[str, Any] | None = None):
|
|
90
90
|
"""
|
|
91
91
|
Log a message at the DEBUG level
|
|
92
92
|
|
|
@@ -97,7 +97,7 @@ class Logger:
|
|
|
97
97
|
# Extract the traceback so we can add the line where this debug message was from
|
|
98
98
|
frame_summary = traceback.extract_stack()[-2]
|
|
99
99
|
|
|
100
|
-
payload:
|
|
100
|
+
payload: dict[str, JsonSerializable] = {
|
|
101
101
|
'filename': frame_summary.filename,
|
|
102
102
|
'func_name': frame_summary.name,
|
|
103
103
|
'lineno': frame_summary.lineno,
|
|
@@ -171,7 +171,7 @@ class LoggingMiddleware(BaseHTTPMiddleware):
|
|
|
171
171
|
return response
|
|
172
172
|
|
|
173
173
|
|
|
174
|
-
def _print_stacktrace(err:
|
|
174
|
+
def _print_stacktrace(err: BaseException | None = None) -> str:
|
|
175
175
|
"""
|
|
176
176
|
Prints out the current stack trace whilst unwinding all the logging calls on the way so it just shows the relevant
|
|
177
177
|
parts. Will also extract any exceptions and print them at the end.
|
|
@@ -207,10 +207,10 @@ class DaraProdFormatter(logging.Formatter):
|
|
|
207
207
|
"""
|
|
208
208
|
|
|
209
209
|
@staticmethod
|
|
210
|
-
def _get_payload(record: logging.LogRecord) ->
|
|
211
|
-
timestamp = time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime(record.created)) + '
|
|
210
|
+
def _get_payload(record: logging.LogRecord) -> dict[str, JsonSerializable]:
|
|
211
|
+
timestamp = time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime(record.created)) + f'.{int(record.msecs)}'
|
|
212
212
|
if isinstance(record.msg, dict):
|
|
213
|
-
payload:
|
|
213
|
+
payload: dict[str, JsonSerializable] = {
|
|
214
214
|
'timestamp': timestamp,
|
|
215
215
|
'level': record.levelno,
|
|
216
216
|
'name': record.name,
|
dara/core/main.py
CHANGED
|
@@ -25,7 +25,6 @@ from contextlib import asynccontextmanager
|
|
|
25
25
|
from importlib.util import find_spec
|
|
26
26
|
from inspect import iscoroutine
|
|
27
27
|
from pathlib import Path
|
|
28
|
-
from typing import Optional
|
|
29
28
|
|
|
30
29
|
from anyio import create_task_group
|
|
31
30
|
from fastapi import FastAPI, HTTPException, Request
|
|
@@ -147,7 +146,7 @@ def _start_application(config: Configuration):
|
|
|
147
146
|
|
|
148
147
|
utils_registry.set('TaskPool', None)
|
|
149
148
|
|
|
150
|
-
task_pool:
|
|
149
|
+
task_pool: TaskPool | None = None
|
|
151
150
|
|
|
152
151
|
# Only initialize the pool if a task module is configured
|
|
153
152
|
if config.task_module:
|
|
@@ -391,7 +390,7 @@ def _start_application(config: Configuration):
|
|
|
391
390
|
|
|
392
391
|
# Catch-all, must add after adding the last api route
|
|
393
392
|
@app.get('/api/{rest_of_path:path}')
|
|
394
|
-
async def not_found():
|
|
393
|
+
async def not_found(rest_of_path: str):
|
|
395
394
|
raise HTTPException(status_code=404, detail='API endpoint not found')
|
|
396
395
|
|
|
397
396
|
# Prepare static template data
|
|
@@ -427,13 +426,13 @@ def _start_application(config: Configuration):
|
|
|
427
426
|
context.update(build_autojs_template(build_cache, config))
|
|
428
427
|
|
|
429
428
|
@app.get('/{full_path:path}', include_in_schema=False, response_class=_TemplateResponse)
|
|
430
|
-
async def serve_app(request: Request):
|
|
429
|
+
async def serve_app(full_path: str, request: Request):
|
|
431
430
|
return jinja_templates.TemplateResponse(request, template_name, context=context)
|
|
432
431
|
|
|
433
432
|
else:
|
|
434
433
|
# Catch-all, must be at the very end
|
|
435
434
|
@app.get('/api/{rest_of_path:path}')
|
|
436
|
-
async def not_found():
|
|
435
|
+
async def not_found(rest_of_path: str):
|
|
437
436
|
raise HTTPException(status_code=404, detail='API endpoint not found')
|
|
438
437
|
|
|
439
438
|
return app
|
dara/core/metrics/cache.py
CHANGED
|
@@ -15,8 +15,6 @@ See the License for the specific language governing permissions and
|
|
|
15
15
|
limitations under the License.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
from typing import Dict, Union
|
|
19
|
-
|
|
20
18
|
from prometheus_client import Info
|
|
21
19
|
|
|
22
20
|
from dara.core.base_definitions import DaraBaseModel as BaseModel
|
|
@@ -24,7 +22,7 @@ from dara.core.base_definitions import DaraBaseModel as BaseModel
|
|
|
24
22
|
cache_metric = Info('cache_size', 'Current size of cache stores and registries', labelnames=['registry_name'])
|
|
25
23
|
|
|
26
24
|
|
|
27
|
-
def format_bytes(num:
|
|
25
|
+
def format_bytes(num: int | float) -> str:
|
|
28
26
|
"""
|
|
29
27
|
Efficient way to format bytes to human readable,
|
|
30
28
|
simplified version of https://stackoverflow.com/a/63839503
|
|
@@ -55,7 +53,7 @@ class CacheMetricsTracker(BaseModel):
|
|
|
55
53
|
Stores and aggregates cache sizes for metrics
|
|
56
54
|
"""
|
|
57
55
|
|
|
58
|
-
registries:
|
|
56
|
+
registries: dict[str, int] = {}
|
|
59
57
|
cache_store: int = 0
|
|
60
58
|
|
|
61
59
|
def update_registry(self, name: str, size: int):
|
dara/core/persistence.py
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
|
-
from collections.abc import Awaitable
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
5
|
from typing import (
|
|
6
6
|
TYPE_CHECKING,
|
|
7
7
|
Any,
|
|
8
|
-
Callable,
|
|
9
|
-
Dict,
|
|
10
|
-
List,
|
|
11
8
|
Literal,
|
|
12
|
-
Optional,
|
|
13
|
-
Set,
|
|
14
|
-
Union,
|
|
15
9
|
)
|
|
16
10
|
from uuid import uuid4
|
|
17
11
|
|
|
@@ -66,7 +60,7 @@ class PersistenceBackend(BaseModel, abc.ABC):
|
|
|
66
60
|
"""
|
|
67
61
|
|
|
68
62
|
@abc.abstractmethod
|
|
69
|
-
async def get_all(self) ->
|
|
63
|
+
async def get_all(self) -> dict[str, Any]:
|
|
70
64
|
"""
|
|
71
65
|
Get all the values as a dictionary of key-value pairs
|
|
72
66
|
"""
|
|
@@ -83,7 +77,7 @@ class InMemoryBackend(PersistenceBackend):
|
|
|
83
77
|
In-memory persistence backend
|
|
84
78
|
"""
|
|
85
79
|
|
|
86
|
-
data:
|
|
80
|
+
data: dict[str, Any] = Field(default_factory=dict)
|
|
87
81
|
|
|
88
82
|
async def has(self, key: str) -> bool:
|
|
89
83
|
return key in self.data
|
|
@@ -124,7 +118,7 @@ class FileBackend(PersistenceBackend):
|
|
|
124
118
|
content = await f.read()
|
|
125
119
|
return json.loads(content) if content else {}
|
|
126
120
|
|
|
127
|
-
async def _write_data(self, data:
|
|
121
|
+
async def _write_data(self, data: dict[str, Any]):
|
|
128
122
|
async with await anyio.open_file(self.path, 'w') as f:
|
|
129
123
|
await f.write(json.dumps(data))
|
|
130
124
|
|
|
@@ -199,16 +193,16 @@ class BackendStore(PersistenceStore):
|
|
|
199
193
|
readonly: bool = False
|
|
200
194
|
|
|
201
195
|
default_value: Any = Field(default=None, exclude=True)
|
|
202
|
-
initialized_scopes:
|
|
203
|
-
sequence_number:
|
|
196
|
+
initialized_scopes: set[str] = Field(default_factory=set, exclude=True)
|
|
197
|
+
sequence_number: dict[str, int] = Field(
|
|
204
198
|
default_factory=dict, exclude=True
|
|
205
199
|
) # Track sequence numbers per user for patch validation
|
|
206
200
|
|
|
207
201
|
def __init__(
|
|
208
202
|
self,
|
|
209
|
-
backend:
|
|
210
|
-
uid:
|
|
211
|
-
scope:
|
|
203
|
+
backend: PersistenceBackend | None = None,
|
|
204
|
+
uid: str | None = None,
|
|
205
|
+
scope: str | None = None,
|
|
212
206
|
readonly: bool = False,
|
|
213
207
|
):
|
|
214
208
|
"""
|
|
@@ -220,7 +214,7 @@ class BackendStore(PersistenceStore):
|
|
|
220
214
|
if 'user' a value is stored per user
|
|
221
215
|
:param readonly: whether to use the backend in read-only mode, i.e. skip syncing values from client to backend and raise if write()/delete() is called
|
|
222
216
|
"""
|
|
223
|
-
kwargs:
|
|
217
|
+
kwargs: dict[str, Any] = {}
|
|
224
218
|
if backend:
|
|
225
219
|
kwargs['backend'] = backend
|
|
226
220
|
|
|
@@ -269,7 +263,7 @@ class BackendStore(PersistenceStore):
|
|
|
269
263
|
|
|
270
264
|
raise ValueError('User not found when trying to compute the key for a user-scoped store')
|
|
271
265
|
|
|
272
|
-
def _get_user(self, key: str) ->
|
|
266
|
+
def _get_user(self, key: str) -> str | None:
|
|
273
267
|
"""
|
|
274
268
|
Get the user for a given key. Returns None if the key is global.
|
|
275
269
|
Reverts the `_get_key` method to get the user for a given key.
|
|
@@ -308,7 +302,7 @@ class BackendStore(PersistenceStore):
|
|
|
308
302
|
|
|
309
303
|
return utils_registry.get('WebsocketManager')
|
|
310
304
|
|
|
311
|
-
def _create_msg(self, scope_key: str, **payload) ->
|
|
305
|
+
def _create_msg(self, scope_key: str, **payload) -> dict[str, Any]:
|
|
312
306
|
"""
|
|
313
307
|
Create a message to send to the frontend.
|
|
314
308
|
:param scope_key: scope key for sequence number
|
|
@@ -333,7 +327,7 @@ class BackendStore(PersistenceStore):
|
|
|
333
327
|
self.sequence_number[key] = current + 1
|
|
334
328
|
return self.sequence_number[key]
|
|
335
329
|
|
|
336
|
-
async def _notify_user(self, user_identifier: str, ignore_channel:
|
|
330
|
+
async def _notify_user(self, user_identifier: str, ignore_channel: str | None = None, **payload):
|
|
337
331
|
"""
|
|
338
332
|
Notify a given user about updates to this store.
|
|
339
333
|
:param user_identifier: user to notify
|
|
@@ -346,7 +340,7 @@ class BackendStore(PersistenceStore):
|
|
|
346
340
|
ignore_channel=ignore_channel,
|
|
347
341
|
)
|
|
348
342
|
|
|
349
|
-
async def _notify_global(self, ignore_channel:
|
|
343
|
+
async def _notify_global(self, ignore_channel: str | None = None, **payload):
|
|
350
344
|
"""
|
|
351
345
|
Notify all users about updates to this store.
|
|
352
346
|
:param ignore_channel: if specified, ignore the specified channel
|
|
@@ -357,7 +351,7 @@ class BackendStore(PersistenceStore):
|
|
|
357
351
|
ignore_channel=ignore_channel,
|
|
358
352
|
)
|
|
359
353
|
|
|
360
|
-
async def _notify_value(self, value: Any, ignore_channel:
|
|
354
|
+
async def _notify_value(self, value: Any, ignore_channel: str | None = None):
|
|
361
355
|
"""
|
|
362
356
|
Notify all clients about the new value for this store.
|
|
363
357
|
Broadcasts to all users if scope is global or sends to the current user if scope is user.
|
|
@@ -377,7 +371,7 @@ class BackendStore(PersistenceStore):
|
|
|
377
371
|
user_identifier = user.identity_id
|
|
378
372
|
return await self._notify_user(user_identifier, value=value, ignore_channel=ignore_channel)
|
|
379
373
|
|
|
380
|
-
async def _notify_patches(self, patches:
|
|
374
|
+
async def _notify_patches(self, patches: list[dict[str, Any]]):
|
|
381
375
|
"""
|
|
382
376
|
Notify all clients about partial updates to this store.
|
|
383
377
|
Broadcasts to all users if scope is global or sends to the current user if scope is user.
|
|
@@ -415,7 +409,7 @@ class BackendStore(PersistenceStore):
|
|
|
415
409
|
|
|
416
410
|
await self.backend.subscribe(_on_value)
|
|
417
411
|
|
|
418
|
-
async def write_partial(self, data:
|
|
412
|
+
async def write_partial(self, data: list[dict[str, Any]] | Any, notify: bool = True, in_place: bool = False):
|
|
419
413
|
"""
|
|
420
414
|
Apply partial updates to the store using JSON Patch operations or automatic diffing.
|
|
421
415
|
|
|
@@ -474,7 +468,7 @@ class BackendStore(PersistenceStore):
|
|
|
474
468
|
|
|
475
469
|
return updated_value
|
|
476
470
|
|
|
477
|
-
async def write(self, value: Any, notify=True, ignore_channel:
|
|
471
|
+
async def write(self, value: Any, notify=True, ignore_channel: str | None = None):
|
|
478
472
|
"""
|
|
479
473
|
Persist a value to the store.
|
|
480
474
|
|
|
@@ -530,7 +524,7 @@ class BackendStore(PersistenceStore):
|
|
|
530
524
|
await self._notify_value(None)
|
|
531
525
|
return await run_user_handler(self.backend.delete, (key,))
|
|
532
526
|
|
|
533
|
-
async def get_all(self) ->
|
|
527
|
+
async def get_all(self) -> dict[str, Any]:
|
|
534
528
|
"""
|
|
535
529
|
Get all the values from the store as a dictionary of key-value pairs.
|
|
536
530
|
|
dara/core/router/compat.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
|
|
3
1
|
from dara.core.definitions import ComponentInstance
|
|
4
2
|
|
|
5
3
|
from .components import Outlet
|
|
@@ -24,7 +22,7 @@ def convert_template_to_router(template):
|
|
|
24
22
|
from dara.core.visual.components.router_content import RouterContent
|
|
25
23
|
|
|
26
24
|
router = Router()
|
|
27
|
-
extracted_routes:
|
|
25
|
+
extracted_routes: list[TemplateRouterContent] = []
|
|
28
26
|
|
|
29
27
|
# Transform the layout: replace RouterContent with Outlet and extract routes
|
|
30
28
|
def transform_component(component):
|
dara/core/router/components.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Annotated, Any, Literal
|
|
1
|
+
from typing import Annotated, Any, Literal
|
|
2
2
|
|
|
3
3
|
from pydantic import BeforeValidator
|
|
4
4
|
|
|
@@ -38,7 +38,7 @@ class Navigate(ComponentInstance):
|
|
|
38
38
|
```
|
|
39
39
|
"""
|
|
40
40
|
|
|
41
|
-
to:
|
|
41
|
+
to: str | RouterPath | ClientVariable
|
|
42
42
|
|
|
43
43
|
replace: bool = False
|
|
44
44
|
"""
|
|
@@ -132,21 +132,21 @@ class Link(StyledComponentInstance):
|
|
|
132
132
|
```
|
|
133
133
|
"""
|
|
134
134
|
|
|
135
|
-
to:
|
|
135
|
+
to: str | RouterPath | ClientVariable
|
|
136
136
|
"""
|
|
137
137
|
Can be a string or RouterPath object
|
|
138
138
|
"""
|
|
139
139
|
|
|
140
140
|
# core anchor element attributes
|
|
141
|
-
target:
|
|
142
|
-
download:
|
|
143
|
-
rel:
|
|
144
|
-
referrer_policy:
|
|
141
|
+
target: str | None = None
|
|
142
|
+
download: str | None = None
|
|
143
|
+
rel: str | None = None
|
|
144
|
+
referrer_policy: str | None = None
|
|
145
145
|
|
|
146
|
-
active_css: Annotated[
|
|
147
|
-
inactive_css: Annotated[
|
|
146
|
+
active_css: Annotated[Any | None, BeforeValidator(transform_raw_css)] = None
|
|
147
|
+
inactive_css: Annotated[Any | None, BeforeValidator(transform_raw_css)] = None
|
|
148
148
|
|
|
149
|
-
def __init__(self, *children:
|
|
149
|
+
def __init__(self, *children: str | ComponentInstance, **kwargs):
|
|
150
150
|
components = list(children)
|
|
151
151
|
if 'children' not in kwargs:
|
|
152
152
|
kwargs['children'] = components
|