dara-core 1.21.16__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.
Files changed (87) hide show
  1. dara/core/auth/base.py +5 -5
  2. dara/core/auth/basic.py +3 -3
  3. dara/core/auth/definitions.py +13 -14
  4. dara/core/auth/routes.py +7 -5
  5. dara/core/auth/utils.py +11 -10
  6. dara/core/base_definitions.py +30 -36
  7. dara/core/cli.py +7 -8
  8. dara/core/configuration.py +51 -58
  9. dara/core/css.py +2 -2
  10. dara/core/data_utils.py +12 -17
  11. dara/core/defaults.py +3 -3
  12. dara/core/definitions.py +58 -63
  13. dara/core/http.py +4 -4
  14. dara/core/interactivity/actions.py +34 -42
  15. dara/core/interactivity/any_data_variable.py +1 -1
  16. dara/core/interactivity/any_variable.py +6 -5
  17. dara/core/interactivity/client_variable.py +1 -2
  18. dara/core/interactivity/condition.py +2 -2
  19. dara/core/interactivity/data_variable.py +2 -4
  20. dara/core/interactivity/derived_data_variable.py +7 -10
  21. dara/core/interactivity/derived_variable.py +45 -51
  22. dara/core/interactivity/filtering.py +19 -19
  23. dara/core/interactivity/loop_variable.py +2 -4
  24. dara/core/interactivity/non_data_variable.py +1 -1
  25. dara/core/interactivity/plain_variable.py +21 -18
  26. dara/core/interactivity/server_variable.py +13 -15
  27. dara/core/interactivity/state_variable.py +4 -5
  28. dara/core/interactivity/switch_variable.py +16 -16
  29. dara/core/interactivity/tabular_variable.py +3 -3
  30. dara/core/interactivity/url_variable.py +3 -3
  31. dara/core/internal/cache_store/cache_store.py +6 -6
  32. dara/core/internal/cache_store/keep_all.py +3 -3
  33. dara/core/internal/cache_store/lru.py +8 -8
  34. dara/core/internal/cache_store/ttl.py +4 -4
  35. dara/core/internal/custom_response.py +3 -3
  36. dara/core/internal/dependency_resolution.py +6 -10
  37. dara/core/internal/devtools.py +2 -3
  38. dara/core/internal/download.py +5 -6
  39. dara/core/internal/encoder_registry.py +7 -11
  40. dara/core/internal/execute_action.py +5 -5
  41. dara/core/internal/hashing.py +1 -2
  42. dara/core/internal/import_discovery.py +7 -9
  43. dara/core/internal/normalization.py +12 -15
  44. dara/core/internal/pandas_utils.py +6 -6
  45. dara/core/internal/pool/channel.py +3 -4
  46. dara/core/internal/pool/definitions.py +9 -9
  47. dara/core/internal/pool/task_pool.py +8 -8
  48. dara/core/internal/pool/utils.py +4 -3
  49. dara/core/internal/pool/worker.py +3 -3
  50. dara/core/internal/registries.py +4 -4
  51. dara/core/internal/registry.py +3 -3
  52. dara/core/internal/registry_lookup.py +4 -4
  53. dara/core/internal/routing.py +23 -22
  54. dara/core/internal/scheduler.py +8 -8
  55. dara/core/internal/settings.py +1 -2
  56. dara/core/internal/store.py +9 -9
  57. dara/core/internal/tasks.py +30 -30
  58. dara/core/internal/utils.py +9 -15
  59. dara/core/internal/websocket.py +18 -18
  60. dara/core/js_tooling/js_utils.py +19 -19
  61. dara/core/logging.py +13 -13
  62. dara/core/main.py +4 -5
  63. dara/core/metrics/cache.py +2 -4
  64. dara/core/persistence.py +19 -25
  65. dara/core/router/compat.py +1 -3
  66. dara/core/router/components.py +10 -10
  67. dara/core/router/dependency_graph.py +2 -4
  68. dara/core/router/router.py +43 -42
  69. dara/core/visual/components/dynamic_component.py +1 -3
  70. dara/core/visual/components/fallback.py +3 -3
  71. dara/core/visual/components/for_cmp.py +5 -5
  72. dara/core/visual/components/menu.py +1 -3
  73. dara/core/visual/components/router_content.py +1 -3
  74. dara/core/visual/components/sidebar_frame.py +8 -10
  75. dara/core/visual/components/theme_provider.py +3 -3
  76. dara/core/visual/components/topbar_frame.py +8 -10
  77. dara/core/visual/css/__init__.py +277 -277
  78. dara/core/visual/dynamic_component.py +18 -22
  79. dara/core/visual/progress_updater.py +1 -1
  80. dara/core/visual/template.py +10 -12
  81. dara/core/visual/themes/definitions.py +46 -46
  82. {dara_core-1.21.16.dist-info → dara_core-1.21.17.dist-info}/METADATA +12 -13
  83. dara_core-1.21.17.dist-info/RECORD +127 -0
  84. dara_core-1.21.16.dist-info/RECORD +0 -127
  85. {dara_core-1.21.16.dist-info → dara_core-1.21.17.dist-info}/LICENSE +0 -0
  86. {dara_core-1.21.16.dist-info → dara_core-1.21.17.dist-info}/WHEEL +0 -0
  87. {dara_core-1.21.16.dist-info → dara_core-1.21.17.dist-info}/entry_points.txt +0 -0
@@ -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, Dict, Literal, Optional, Set, Tuple, Union
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: Optional[int] = None
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: Optional[str] = Field(default=None, alias='__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 = Union[DaraClientMessage, CustomClientMessage]
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: Optional[str] = Field(default=None, alias='__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: Optional[str] = Field(default=None, alias='__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 = Union[ServerMessagePayload, CustomServerMessagePayload]
144
- LoosePayload = Union[ServerPayload, dict]
145
- ServerMessage = Union[DaraServerMessage, CustomServerMessage]
143
+ ServerPayload = ServerMessagePayload | CustomServerMessagePayload
144
+ LoosePayload = ServerPayload | dict
145
+ ServerMessage = DaraServerMessage | CustomServerMessage
146
146
 
147
- WS_CHANNEL: ContextVar[Optional[str]] = ContextVar('ws_channel', default=None)
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: Dict[str, Tuple[Event, Optional[Any]]]
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) -> Optional[Any]:
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) -> Set[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: Set[str] = set()
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: Dict[str, WebSocketHandler] = {}
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: Optional[str] = None):
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: Optional[str] = None
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: Optional[str] = Query(default=None)):
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
 
@@ -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, Dict, List, Literal, Optional, Set, Union, cast
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: Dict[str, str]
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: Optional[JsConfig] = None
80
+ js_config: JsConfig | None = None
81
81
  """Custom JS configuration from dara.config.json file"""
82
82
 
83
- npm_registry: Optional[str] = None
83
+ npm_registry: str | None = None
84
84
  """Optional npm registry url to pull packages from"""
85
85
 
86
- npm_token: Optional[str] = None
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: Set[BuildCacheKey]
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: Set[BuildCacheKey] = set(['static_files_dir', 'package_map', 'build_config'])
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: List[str]
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: Dict[str, str]
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: Optional[BuildConfig] = None):
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) -> Optional[str]:
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) -> Dict[str, str]:
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) -> List[str]:
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) -> Dict[str, Any]:
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: Optional[Union['BuildCache', str]] = None) -> BuildCacheDiff:
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: Set[BuildCacheKey] = set()
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: Union[BuildCacheDiff, None] = None):
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) -> Dict[str, Any]:
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: Dict[str, List[str]] = {
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, Dict, Optional, Union
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 = Union[str, int, float, dict, None]
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: Optional[Dict[str, Any]] = None):
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: Dict[str, JsonSerializable] = {
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: Optional[Dict[str, Any]] = None):
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: Dict[str, JsonSerializable] = {
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: Optional[Dict[str, Any]] = None):
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: Optional[str] = None, extra: Optional[Dict[str, Any]] = None):
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: Dict[str, JsonSerializable] = {
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: Optional[BaseException] = None) -> str:
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) -> Dict[str, JsonSerializable]:
211
- timestamp = time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime(record.created)) + '.%s' % int(record.msecs)
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: Dict[str, JsonSerializable] = {
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: Optional[TaskPool] = None
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
@@ -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: Union[int, float]) -> str:
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: Dict[str, int] = {}
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) -> Dict[str, Any]:
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: Dict[str, Any] = Field(default_factory=dict)
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: Dict[str, Any]):
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: Set[str] = Field(default_factory=set, exclude=True)
203
- sequence_number: Dict[str, int] = Field(
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: Optional[PersistenceBackend] = None,
210
- uid: Optional[str] = None,
211
- scope: Optional[str] = None,
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: Dict[str, Any] = {}
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) -> Optional[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) -> Dict[str, Any]:
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: Optional[str] = None, **payload):
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: Optional[str] = None, **payload):
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: Optional[str] = None):
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: List[Dict[str, Any]]):
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: Union[List[Dict[str, Any]], Any], notify: bool = True, in_place: bool = False):
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: Optional[str] = None):
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) -> Dict[str, Any]:
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
 
@@ -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: List[TemplateRouterContent] = []
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):
@@ -1,4 +1,4 @@
1
- from typing import Annotated, Any, Literal, Optional, Union
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: Union[str, RouterPath, ClientVariable]
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: Union[str, RouterPath, ClientVariable]
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: Optional[str] = None
142
- download: Optional[str] = None
143
- rel: Optional[str] = None
144
- referrer_policy: Optional[str] = None
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[Optional[Any], BeforeValidator(transform_raw_css)] = None
147
- inactive_css: Annotated[Optional[Any], BeforeValidator(transform_raw_css)] = None
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: Union[str, ComponentInstance], **kwargs):
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