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/internal/utils.py
CHANGED
|
@@ -20,7 +20,6 @@ from __future__ import annotations
|
|
|
20
20
|
import asyncio
|
|
21
21
|
import inspect
|
|
22
22
|
import os
|
|
23
|
-
from collections.abc import Awaitable, Coroutine, Sequence
|
|
24
23
|
from functools import wraps
|
|
25
24
|
from importlib import import_module
|
|
26
25
|
from importlib.util import find_spec
|
|
@@ -28,21 +27,21 @@ from types import ModuleType
|
|
|
28
27
|
from typing import (
|
|
29
28
|
TYPE_CHECKING,
|
|
30
29
|
Any,
|
|
30
|
+
Awaitable,
|
|
31
31
|
Callable,
|
|
32
|
+
Coroutine,
|
|
32
33
|
Dict,
|
|
33
34
|
Literal,
|
|
34
35
|
Optional,
|
|
36
|
+
Sequence,
|
|
35
37
|
Tuple,
|
|
36
|
-
Type,
|
|
37
|
-
TypeVar,
|
|
38
38
|
Union,
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
import anyio
|
|
42
42
|
from anyio import from_thread
|
|
43
|
-
from exceptiongroup import
|
|
43
|
+
from exceptiongroup import ExceptionGroup
|
|
44
44
|
from starlette.concurrency import run_in_threadpool
|
|
45
|
-
from typing_extensions import ParamSpec
|
|
46
45
|
|
|
47
46
|
from dara.core.auth.definitions import SESSION_ID, USER
|
|
48
47
|
from dara.core.base_definitions import CacheType
|
|
@@ -80,7 +79,7 @@ def get_cache_scope(cache_type: Optional[CacheType]) -> CacheScope:
|
|
|
80
79
|
return 'global'
|
|
81
80
|
|
|
82
81
|
|
|
83
|
-
async def run_user_handler(handler: Callable, args:
|
|
82
|
+
async def run_user_handler(handler: Callable, args: Sequence = [], kwargs: dict = {}):
|
|
84
83
|
"""
|
|
85
84
|
Run a user-defined handler function. Runs sync functions in a threadpool.
|
|
86
85
|
Handles SystemExits cleanly.
|
|
@@ -89,10 +88,6 @@ async def run_user_handler(handler: Callable, args: Union[Sequence, None] = None
|
|
|
89
88
|
:param args: list of arguments to pass to the function
|
|
90
89
|
:param kwargs: dict of kwargs to past to the function
|
|
91
90
|
"""
|
|
92
|
-
if args is None:
|
|
93
|
-
args = []
|
|
94
|
-
if kwargs is None:
|
|
95
|
-
kwargs = {}
|
|
96
91
|
with handle_system_exit('User defined function quit unexpectedly'):
|
|
97
92
|
if inspect.iscoroutinefunction(handler):
|
|
98
93
|
return await handler(*args, **kwargs)
|
|
@@ -169,21 +164,17 @@ def enforce_sso(conf: ConfigurationBuilder):
|
|
|
169
164
|
Raises if SSO is not used
|
|
170
165
|
"""
|
|
171
166
|
try:
|
|
172
|
-
from dara.enterprise import SSOAuthConfig
|
|
167
|
+
from dara.enterprise import SSOAuthConfig
|
|
173
168
|
|
|
174
169
|
if conf.auth_config is None or not isinstance(conf.auth_config, SSOAuthConfig):
|
|
175
170
|
raise ValueError('Config does not have SSO auth enabled. Please update your application to configure SSO.')
|
|
176
|
-
except ImportError
|
|
171
|
+
except ImportError:
|
|
177
172
|
raise ValueError(
|
|
178
173
|
'SSO is not enabled. Please install the dara_enterprise package and configure SSO to use this feature.'
|
|
179
|
-
)
|
|
174
|
+
)
|
|
180
175
|
|
|
181
176
|
|
|
182
|
-
|
|
183
|
-
T = TypeVar('T')
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def async_dedupe(fn: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
|
|
177
|
+
def async_dedupe(fn: Callable[..., Awaitable]):
|
|
187
178
|
"""
|
|
188
179
|
Decorator to deduplicate concurrent calls to asynchronous functions based on their arguments.
|
|
189
180
|
|
|
@@ -201,7 +192,7 @@ def async_dedupe(fn: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
|
|
|
201
192
|
is_method = 'self' in inspect.signature(fn).parameters
|
|
202
193
|
|
|
203
194
|
@wraps(fn)
|
|
204
|
-
async def wrapped(*args
|
|
195
|
+
async def wrapped(*args, **kwargs):
|
|
205
196
|
non_self_args = args[1:] if is_method else args
|
|
206
197
|
key = (non_self_args, frozenset(kwargs.items()))
|
|
207
198
|
lock = locks.get(key)
|
|
@@ -237,22 +228,8 @@ def resolve_exception_group(error: Any):
|
|
|
237
228
|
|
|
238
229
|
:param error: The error to resolve
|
|
239
230
|
"""
|
|
240
|
-
if isinstance(error, ExceptionGroup)
|
|
241
|
-
|
|
231
|
+
if isinstance(error, ExceptionGroup):
|
|
232
|
+
if len(error.exceptions) == 1:
|
|
233
|
+
return resolve_exception_group(error.exceptions[0])
|
|
242
234
|
|
|
243
235
|
return error
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
def exception_group_contains(err_type: Type[BaseException], group: BaseExceptionGroup) -> bool:
|
|
247
|
-
"""
|
|
248
|
-
Check if an ExceptionGroup contains an error of a given type, recursively
|
|
249
|
-
|
|
250
|
-
:param err_type: The type of error to check for
|
|
251
|
-
:param group: The ExceptionGroup to check
|
|
252
|
-
"""
|
|
253
|
-
for exc in group.exceptions:
|
|
254
|
-
if isinstance(exc, err_type):
|
|
255
|
-
return True
|
|
256
|
-
if isinstance(exc, BaseExceptionGroup):
|
|
257
|
-
return exception_group_contains(err_type, exc)
|
|
258
|
-
return False
|
dara/core/internal/websocket.py
CHANGED
|
@@ -204,27 +204,28 @@ class WebSocketHandler:
|
|
|
204
204
|
message_id = message.channel
|
|
205
205
|
|
|
206
206
|
# If the message has a channel ID, it's a response to a previous message
|
|
207
|
-
if message_id
|
|
208
|
-
|
|
207
|
+
if message_id:
|
|
208
|
+
if message_id in self.pending_responses:
|
|
209
|
+
event, existing_messages = self.pending_responses[message_id]
|
|
210
|
+
|
|
211
|
+
# If the response is chunked then collect the messages in pending responses
|
|
212
|
+
if message.chunk_count is not None:
|
|
213
|
+
if existing_messages is not None and isinstance(existing_messages, list):
|
|
214
|
+
existing_messages.append(message.message)
|
|
215
|
+
else:
|
|
216
|
+
existing_messages = [message.message]
|
|
217
|
+
self.pending_responses[message_id] = (
|
|
218
|
+
event,
|
|
219
|
+
existing_messages,
|
|
220
|
+
)
|
|
209
221
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
existing_messages.append(message.message)
|
|
222
|
+
# If all chunks have been received, set the event to notify the waiting coroutine
|
|
223
|
+
if len(existing_messages) == message.chunk_count:
|
|
224
|
+
event.set()
|
|
214
225
|
else:
|
|
215
|
-
|
|
216
|
-
self.pending_responses[message_id] = (
|
|
217
|
-
event,
|
|
218
|
-
existing_messages,
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
# If all chunks have been received, set the event to notify the waiting coroutine
|
|
222
|
-
if len(existing_messages) == message.chunk_count:
|
|
226
|
+
# Store the response and set the event to notify the waiting coroutine
|
|
227
|
+
self.pending_responses[message_id] = (event, message.message)
|
|
223
228
|
event.set()
|
|
224
|
-
else:
|
|
225
|
-
# Store the response and set the event to notify the waiting coroutine
|
|
226
|
-
self.pending_responses[message_id] = (event, message.message)
|
|
227
|
-
event.set()
|
|
228
229
|
|
|
229
230
|
return None
|
|
230
231
|
|
|
@@ -474,7 +475,7 @@ async def ws_handler(websocket: WebSocket, token: Optional[str] = Query(default=
|
|
|
474
475
|
if pending_tokens_registry.has(token):
|
|
475
476
|
pending_tokens_registry.remove(token)
|
|
476
477
|
|
|
477
|
-
user_identifier = token_content.identity_id
|
|
478
|
+
user_identifier = token_content.identity_id or token_content.identity_name
|
|
478
479
|
|
|
479
480
|
# Add the new session ID to known sessions for that user
|
|
480
481
|
if sessions_registry.has(user_identifier):
|
|
@@ -496,8 +497,6 @@ async def ws_handler(websocket: WebSocket, token: Optional[str] = Query(default=
|
|
|
496
497
|
SESSION_ID.set(token_data.session_id)
|
|
497
498
|
ID_TOKEN.set(token_data.id_token)
|
|
498
499
|
|
|
499
|
-
WS_CHANNEL.set(channel)
|
|
500
|
-
|
|
501
500
|
# Set initial Auth context vars for the WS connection
|
|
502
501
|
update_context(token_content)
|
|
503
502
|
|
dara/core/js_tooling/js_utils.py
CHANGED
|
@@ -15,7 +15,6 @@ See the License for the specific language governing permissions and
|
|
|
15
15
|
limitations under the License.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
import contextlib
|
|
19
18
|
import importlib
|
|
20
19
|
import json
|
|
21
20
|
import os
|
|
@@ -237,8 +236,10 @@ class BuildCache(BaseModel):
|
|
|
237
236
|
|
|
238
237
|
# Create a symlink from the custom js folder into the static files directory
|
|
239
238
|
new_path = os.path.abspath(os.path.join(self.static_files_dir, self.build_config.js_config.local_entry))
|
|
240
|
-
|
|
239
|
+
try:
|
|
241
240
|
os.unlink(new_path)
|
|
241
|
+
except FileNotFoundError:
|
|
242
|
+
pass
|
|
242
243
|
os.symlink(absolute_path, new_path)
|
|
243
244
|
|
|
244
245
|
# Create a symlink for the node modules in the custom_js folder
|
|
@@ -246,8 +247,10 @@ class BuildCache(BaseModel):
|
|
|
246
247
|
new_node_modules_path = os.path.abspath(
|
|
247
248
|
os.path.join(os.getcwd(), self.build_config.js_config.local_entry, 'node_modules')
|
|
248
249
|
)
|
|
249
|
-
|
|
250
|
+
try:
|
|
250
251
|
os.unlink(new_node_modules_path)
|
|
252
|
+
except FileNotFoundError:
|
|
253
|
+
pass
|
|
251
254
|
os.symlink(node_modules_path, new_node_modules_path)
|
|
252
255
|
|
|
253
256
|
def get_importers(self) -> Dict[str, str]:
|
|
@@ -271,7 +274,7 @@ class BuildCache(BaseModel):
|
|
|
271
274
|
"""
|
|
272
275
|
py_modules = set()
|
|
273
276
|
|
|
274
|
-
for module in self.package_map:
|
|
277
|
+
for module in self.package_map.keys():
|
|
275
278
|
py_modules.add(module)
|
|
276
279
|
|
|
277
280
|
if 'dara.core' in py_modules:
|
|
@@ -313,9 +316,8 @@ class BuildCache(BaseModel):
|
|
|
313
316
|
# Append core deps required for building/dev mode
|
|
314
317
|
pkg_json['dependencies'] = {
|
|
315
318
|
**deps,
|
|
316
|
-
'@vitejs/plugin-react': '
|
|
317
|
-
'vite': '
|
|
318
|
-
'vite-plugin-node-polyfills': '0.24.0',
|
|
319
|
+
'@vitejs/plugin-react': '2.1.0',
|
|
320
|
+
'vite': '3.1.8',
|
|
319
321
|
}
|
|
320
322
|
|
|
321
323
|
return pkg_json
|
|
@@ -432,16 +434,13 @@ def _get_module_file(module: str) -> str:
|
|
|
432
434
|
return cast(str, imported_module.__file__)
|
|
433
435
|
|
|
434
436
|
|
|
435
|
-
def rebuild_js(build_cache: BuildCache, build_diff:
|
|
437
|
+
def rebuild_js(build_cache: BuildCache, build_diff: BuildCacheDiff = BuildCacheDiff.full_diff()):
|
|
436
438
|
"""
|
|
437
439
|
Generic 'rebuild' function which bundles/prepares assets depending on the build mode chosen
|
|
438
440
|
|
|
439
441
|
:param build_cache: current build configuration cache
|
|
440
442
|
:param build_diff: the difference between the current build cache and the previous build cache
|
|
441
443
|
"""
|
|
442
|
-
if build_diff is None:
|
|
443
|
-
build_diff = BuildCacheDiff.full_diff()
|
|
444
|
-
|
|
445
444
|
# If we are in docker mode, skip the JS build
|
|
446
445
|
if os.environ.get('DARA_DOCKER_MODE', 'FALSE') == 'TRUE':
|
|
447
446
|
dev_logger.debug('Docker mode, skipping JS build')
|
|
@@ -486,16 +485,17 @@ def bundle_js(build_cache: BuildCache, copy_js: bool = False):
|
|
|
486
485
|
:param copy_js: whether to copy JS instead of symlinking it
|
|
487
486
|
"""
|
|
488
487
|
# If custom JS is present, symlink it
|
|
489
|
-
if build_cache.build_config.js_config is not None
|
|
490
|
-
if
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
488
|
+
if build_cache.build_config.js_config is not None:
|
|
489
|
+
if os.path.isdir(build_cache.build_config.js_config.local_entry):
|
|
490
|
+
if copy_js:
|
|
491
|
+
# Just move the directory to output
|
|
492
|
+
js_folder_name = os.path.basename(build_cache.build_config.js_config.local_entry)
|
|
493
|
+
shutil.copytree(
|
|
494
|
+
build_cache.build_config.js_config.local_entry,
|
|
495
|
+
os.path.join(build_cache.static_files_dir, js_folder_name),
|
|
496
|
+
)
|
|
497
|
+
else:
|
|
498
|
+
build_cache.symlink_js()
|
|
499
499
|
|
|
500
500
|
# Determine template paths
|
|
501
501
|
entry_template = os.path.join(pathlib.Path(__file__).parent.absolute(), 'templates/_entry.template.tsx')
|
|
@@ -544,18 +544,16 @@ def bundle_js(build_cache: BuildCache, copy_js: bool = False):
|
|
|
544
544
|
|
|
545
545
|
cwd = os.getcwd()
|
|
546
546
|
os.chdir(build_cache.static_files_dir)
|
|
547
|
-
|
|
548
|
-
exit_code = os.system(f'{package_manager} install') # nosec B605 # package_manager is validated
|
|
547
|
+
exit_code = os.system(f'{package_manager} install') # nosec B605 # package_manager is validated
|
|
549
548
|
if exit_code > 0:
|
|
550
549
|
raise SystemError(
|
|
551
550
|
"Failed to install the JS dependencies - there's likely a connection issue or a broken package"
|
|
552
551
|
)
|
|
553
|
-
dev_logger.info('JS dependencies installed successfully')
|
|
554
552
|
|
|
555
553
|
# Load entry template as a string
|
|
556
|
-
with open(entry_template, encoding='utf-8') as f:
|
|
554
|
+
with open(entry_template, 'r', encoding='utf-8') as f:
|
|
557
555
|
entry_template_str = f.read()
|
|
558
|
-
with open(vite_template, encoding='utf-8') as f:
|
|
556
|
+
with open(vite_template, 'r', encoding='utf-8') as f:
|
|
559
557
|
vite_template_str = f.read()
|
|
560
558
|
|
|
561
559
|
# Convert importers dict to a string for injection into the template
|
|
@@ -576,7 +574,7 @@ def bundle_js(build_cache: BuildCache, copy_js: bool = False):
|
|
|
576
574
|
dev_logger.warning('App is in DEV mode, running `dara dev` CLI command alongside this process is required')
|
|
577
575
|
else:
|
|
578
576
|
# Run build pointed at the generated entry file
|
|
579
|
-
exit_code = os.system(f'{package_manager} run build')
|
|
577
|
+
exit_code = os.system(f'{package_manager} run build') # nosec B605 # package_manager is validated
|
|
580
578
|
if exit_code > 0:
|
|
581
579
|
raise SystemError('Failed to build the JS part of the project')
|
|
582
580
|
|
|
@@ -642,7 +640,7 @@ def build_autojs_template(html_template: str, build_cache: BuildCache, config: C
|
|
|
642
640
|
"""
|
|
643
641
|
settings = get_settings()
|
|
644
642
|
entry_template = os.path.join(pathlib.Path(__file__).parent.absolute(), 'templates/_entry_autojs.template.tsx')
|
|
645
|
-
with open(entry_template, encoding='utf-8') as f:
|
|
643
|
+
with open(entry_template, 'r', encoding='utf-8') as f:
|
|
646
644
|
entry_template_str = f.read()
|
|
647
645
|
|
|
648
646
|
importers_dict = build_cache.get_importers()
|
|
@@ -1,27 +1,18 @@
|
|
|
1
1
|
import react from '@vitejs/plugin-react';
|
|
2
2
|
import { defineConfig } from 'vite';
|
|
3
|
-
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
|
4
3
|
|
|
5
4
|
export default defineConfig({
|
|
6
5
|
base: '',
|
|
7
6
|
plugins: [
|
|
8
7
|
react({
|
|
9
8
|
jsxRuntime: 'classic',
|
|
10
|
-
})
|
|
11
|
-
// Some package we're pulling requires node polyfills for stream
|
|
12
|
-
nodePolyfills({
|
|
13
|
-
globals: {
|
|
14
|
-
process: true,
|
|
15
|
-
Buffer: true,
|
|
16
|
-
global: true,
|
|
17
|
-
},
|
|
18
|
-
}),
|
|
9
|
+
})
|
|
19
10
|
],
|
|
20
11
|
publicDir: false,
|
|
21
12
|
build: {
|
|
22
13
|
outDir: '$$output$$',
|
|
23
14
|
assetsDir: '',
|
|
24
|
-
manifest:
|
|
15
|
+
manifest: true,
|
|
25
16
|
rollupOptions: {
|
|
26
17
|
input: './_entry.tsx',
|
|
27
18
|
},
|
|
@@ -44,5 +35,5 @@ export default defineConfig({
|
|
|
44
35
|
},
|
|
45
36
|
worker: {
|
|
46
37
|
format: 'es',
|
|
47
|
-
}
|
|
38
|
+
}
|
|
48
39
|
});
|
dara/core/logging.py
CHANGED
|
@@ -74,7 +74,7 @@ class Logger:
|
|
|
74
74
|
|
|
75
75
|
self._logger.warning(payload, extra={'content': extra})
|
|
76
76
|
|
|
77
|
-
def error(self, title: str, error:
|
|
77
|
+
def error(self, title: str, error: Exception, extra: Optional[Dict[str, Any]] = None):
|
|
78
78
|
"""
|
|
79
79
|
Log a message at the ERROR level
|
|
80
80
|
|
|
@@ -135,7 +135,6 @@ class LoggingMiddleware(BaseHTTPMiddleware):
|
|
|
135
135
|
# This is required so that requesting the body content doesn't hang the request
|
|
136
136
|
if request.headers.get('Content-Type') == 'application/json' and content_length < one_mb:
|
|
137
137
|
old_recieve = request._receive
|
|
138
|
-
|
|
139
138
|
# Add the debug logging into a new receive call that wraps the old one. This is required to make
|
|
140
139
|
# streaming requests and responses work as streaming sends further messages to trigger
|
|
141
140
|
# sending/receiving further data
|
|
@@ -171,14 +170,11 @@ class LoggingMiddleware(BaseHTTPMiddleware):
|
|
|
171
170
|
return response
|
|
172
171
|
|
|
173
172
|
|
|
174
|
-
def _print_stacktrace(
|
|
173
|
+
def _print_stacktrace():
|
|
175
174
|
"""
|
|
176
175
|
Prints out the current stack trace whilst unwinding all the logging calls on the way so it just shows the relevant
|
|
177
176
|
parts. Will also extract any exceptions and print them at the end.
|
|
178
177
|
"""
|
|
179
|
-
if err is not None:
|
|
180
|
-
return ''.join(traceback.format_exception(type(err), err, err.__traceback__))
|
|
181
|
-
|
|
182
178
|
exc = sys.exc_info()[0]
|
|
183
179
|
stack = traceback.extract_stack()[:-1]
|
|
184
180
|
if exc is not None:
|
|
@@ -196,7 +192,7 @@ def _print_stacktrace(err: Optional[BaseException] = None) -> str:
|
|
|
196
192
|
trc = 'Traceback (most recent call last):\n'
|
|
197
193
|
stackstr = trc + ''.join(traceback.format_list(stack))
|
|
198
194
|
if exc is not None:
|
|
199
|
-
stackstr += ' ' + traceback.format_exc().lstrip(trc)
|
|
195
|
+
stackstr += ' ' + traceback.format_exc().lstrip(trc) # pylint:disable=bad-str-strip-call
|
|
200
196
|
return stackstr
|
|
201
197
|
|
|
202
198
|
|
|
@@ -208,7 +204,11 @@ class DaraProdFormatter(logging.Formatter):
|
|
|
208
204
|
|
|
209
205
|
@staticmethod
|
|
210
206
|
def _get_payload(record: logging.LogRecord) -> Dict[str, JsonSerializable]:
|
|
211
|
-
timestamp = time.strftime(
|
|
207
|
+
timestamp = time.strftime(
|
|
208
|
+
'%Y-%m-%dT%H:%M:%S', time.localtime(record.created)
|
|
209
|
+
) + '.%s' % int( # pylint:disable=consider-using-f-string
|
|
210
|
+
record.msecs
|
|
211
|
+
)
|
|
212
212
|
if isinstance(record.msg, dict):
|
|
213
213
|
payload: Dict[str, JsonSerializable] = {
|
|
214
214
|
'timestamp': timestamp,
|
|
@@ -218,9 +218,8 @@ class DaraProdFormatter(logging.Formatter):
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
if record.levelname == 'ERROR':
|
|
221
|
-
|
|
222
|
-
payload['
|
|
223
|
-
payload['stacktrace'] = _print_stacktrace(err if isinstance(err, BaseException) else None)
|
|
221
|
+
payload['error'] = str(payload.pop('error'))
|
|
222
|
+
payload['stacktrace'] = _print_stacktrace()
|
|
224
223
|
|
|
225
224
|
return payload
|
|
226
225
|
|
|
@@ -285,8 +284,8 @@ class DaraDevFormatter(logging.Formatter):
|
|
|
285
284
|
|
|
286
285
|
if record.levelname == 'ERROR':
|
|
287
286
|
error = ''
|
|
288
|
-
if
|
|
289
|
-
error = _print_stacktrace(
|
|
287
|
+
if payload.get('error'):
|
|
288
|
+
error = _print_stacktrace()
|
|
290
289
|
content = base_msg
|
|
291
290
|
if record.__dict__.get('content'):
|
|
292
291
|
content = content + '\r\n' + self.extra_template % (spacer, record.__dict__['content'])
|
dara/core/main.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 asyncio
|
|
19
18
|
import os
|
|
20
19
|
import sys
|
|
@@ -92,11 +91,10 @@ def _start_application(config: Configuration):
|
|
|
92
91
|
os.environ['VITE_MANIFEST_PATH'] = f'{config.static_files_dir}/manifest.json'
|
|
93
92
|
os.environ['VITE_STATIC_PATH'] = config.static_files_dir
|
|
94
93
|
import fastapi_vite_dara
|
|
95
|
-
import fastapi_vite_dara.config
|
|
96
94
|
|
|
97
95
|
if len(config.pages) > 0:
|
|
98
96
|
BASE_DIR = Path(__file__).parent
|
|
99
|
-
jinja_templates = Jinja2Templates(directory=str(Path(BASE_DIR, 'jinja')))
|
|
97
|
+
jinja_templates = Jinja2Templates(directory=str((Path(BASE_DIR, 'jinja'))))
|
|
100
98
|
jinja_templates.env.globals['vite_hmr_client'] = fastapi_vite_dara.vite_hmr_client
|
|
101
99
|
jinja_templates.env.globals['vite_asset'] = fastapi_vite_dara.vite_asset
|
|
102
100
|
jinja_templates.env.globals['static_url'] = fastapi_vite_dara.config.settings.static_url
|
|
@@ -109,7 +107,7 @@ def _start_application(config: Configuration):
|
|
|
109
107
|
|
|
110
108
|
# Configure the default executor for threads run via the async loop
|
|
111
109
|
loop = asyncio.get_event_loop()
|
|
112
|
-
loop.set_default_executor(ThreadPoolExecutor(max_workers=int(os.environ.get('DARA_NUM_COMPONENT_THREADS',
|
|
110
|
+
loop.set_default_executor(ThreadPoolExecutor(max_workers=int(os.environ.get('DARA_NUM_COMPONENT_THREADS', 8))))
|
|
113
111
|
|
|
114
112
|
is_production = os.environ.get('DARA_DOCKER_MODE') == 'TRUE'
|
|
115
113
|
|
|
@@ -171,7 +169,7 @@ def _start_application(config: Configuration):
|
|
|
171
169
|
worker_parameters={'task_module': config.task_module},
|
|
172
170
|
max_workers=max_workers,
|
|
173
171
|
)
|
|
174
|
-
await task_pool.start(60)
|
|
172
|
+
await task_pool.start(60) # timeout after 60s
|
|
175
173
|
utils_registry.set('TaskPool', task_pool)
|
|
176
174
|
dev_logger.info('Task pool initialized')
|
|
177
175
|
|
|
@@ -339,15 +337,15 @@ def _start_application(config: Configuration):
|
|
|
339
337
|
|
|
340
338
|
# Start metrics server in a daemon thread
|
|
341
339
|
if os.environ.get('DARA_DISABLE_METRICS') != 'TRUE' and os.environ.get('DARA_TEST_FLAG', None) is None:
|
|
342
|
-
port = int(os.environ.get('DARA_METRICS_PORT',
|
|
340
|
+
port = int(os.environ.get('DARA_METRICS_PORT', 10000))
|
|
343
341
|
start_http_server(port)
|
|
344
342
|
|
|
345
343
|
# Start profiling server in a daemon thread if explicitly enabled (only works on linux)
|
|
346
344
|
if os.environ.get('DARA_PYPPROF_PORT', None) is not None:
|
|
347
|
-
profiling_port = int(os.environ.get('DARA_PYPPROF_PORT',
|
|
345
|
+
profiling_port = int(os.environ.get('DARA_PYPPROF_PORT', 10001))
|
|
348
346
|
dev_logger.warning('Starting cpu/memory profiling server', extra={'port': profiling_port})
|
|
349
347
|
|
|
350
|
-
from pypprof.net_http import start_pprof_server
|
|
348
|
+
from pypprof.net_http import start_pprof_server
|
|
351
349
|
|
|
352
350
|
start_pprof_server(port=profiling_port)
|
|
353
351
|
|
|
@@ -360,7 +358,7 @@ def _start_application(config: Configuration):
|
|
|
360
358
|
app.include_router(core_api_router, prefix='/api/core')
|
|
361
359
|
|
|
362
360
|
@app.get('/api/{rest_of_path:path}')
|
|
363
|
-
async def not_found():
|
|
361
|
+
async def not_found(): # pylint: disable=unused-variable
|
|
364
362
|
raise HTTPException(status_code=404, detail='API endpoint not found')
|
|
365
363
|
|
|
366
364
|
if len(config.pages) > 0:
|
|
@@ -371,23 +369,22 @@ def _start_application(config: Configuration):
|
|
|
371
369
|
# Auto-js mode - serve the built template with UMDs
|
|
372
370
|
if build_cache.build_config.mode == BuildMode.AUTO_JS:
|
|
373
371
|
# Load template
|
|
374
|
-
|
|
375
|
-
with open(template_path, encoding='utf-8') as fp:
|
|
372
|
+
with open(os.path.join(Path(BASE_DIR, 'jinja'), 'index_autojs.html'), 'r', encoding='utf-8') as fp:
|
|
376
373
|
template = fp.read()
|
|
377
374
|
|
|
378
375
|
# Generate tags for the template
|
|
379
376
|
template = build_autojs_template(template, build_cache, config)
|
|
380
377
|
|
|
381
378
|
@app.get('/{full_path:path}', include_in_schema=False, response_class=HTMLResponse)
|
|
382
|
-
async def serve_app(request: Request): #
|
|
379
|
+
async def serve_app(request: Request): # pylint: disable=unused-variable
|
|
383
380
|
return HTMLResponse(template)
|
|
384
381
|
|
|
385
382
|
else:
|
|
386
383
|
# Otherwise serve the Vite template
|
|
387
384
|
|
|
388
385
|
@app.get('/{full_path:path}', include_in_schema=False, response_class=_TemplateResponse)
|
|
389
|
-
async def serve_app(request: Request): #
|
|
390
|
-
return jinja_templates.TemplateResponse(request, 'index.html')
|
|
386
|
+
async def serve_app(request: Request): # pylint: disable=unused-variable
|
|
387
|
+
return jinja_templates.TemplateResponse(request, 'index.html')
|
|
391
388
|
|
|
392
389
|
return app
|
|
393
390
|
|
dara/core/metrics/cache.py
CHANGED
|
@@ -47,7 +47,7 @@ def format_bytes(num: Union[int, float]) -> str:
|
|
|
47
47
|
# We only shrink the number if we HAVEN'T reached the last unit.
|
|
48
48
|
num /= unit_step
|
|
49
49
|
|
|
50
|
-
return f'{num:.2f} {unit}'
|
|
50
|
+
return f'{num:.2f} {unit}'
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
class CacheMetricsTracker(BaseModel):
|
dara/core/metrics/utils.py
CHANGED
|
@@ -51,11 +51,11 @@ def total_size(o: object):
|
|
|
51
51
|
|
|
52
52
|
try:
|
|
53
53
|
all_handlers = {tuple: iter, list: iter, dict: dict_handler, set: iter, BaseModel: pydantic_handler}
|
|
54
|
-
seen = set()
|
|
55
|
-
default_size = getsizeof(0)
|
|
54
|
+
seen = set() # track which object id's have already been seen
|
|
55
|
+
default_size = getsizeof(0) # estimate sizeof object without __sizeof__
|
|
56
56
|
|
|
57
57
|
def sizeof(o):
|
|
58
|
-
if id(o) in seen:
|
|
58
|
+
if id(o) in seen: # do not double count the same object
|
|
59
59
|
return 0
|
|
60
60
|
seen.add(id(o))
|
|
61
61
|
s = getsizeof(o, default_size)
|
dara/core/persistence.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
|
-
from collections.abc import Awaitable
|
|
5
4
|
from typing import (
|
|
6
5
|
TYPE_CHECKING,
|
|
7
6
|
Any,
|
|
7
|
+
Awaitable,
|
|
8
8
|
Callable,
|
|
9
9
|
Dict,
|
|
10
10
|
List,
|
|
@@ -255,7 +255,7 @@ class BackendStore(PersistenceStore):
|
|
|
255
255
|
user = USER.get()
|
|
256
256
|
|
|
257
257
|
if user:
|
|
258
|
-
user_key = user.identity_id
|
|
258
|
+
user_key = user.identity_id or user.identity_name
|
|
259
259
|
|
|
260
260
|
# Make sure the store is initialized
|
|
261
261
|
if user_key not in self.initialized_scopes:
|
|
@@ -277,7 +277,7 @@ class BackendStore(PersistenceStore):
|
|
|
277
277
|
if key == 'global':
|
|
278
278
|
return None
|
|
279
279
|
|
|
280
|
-
# otherwise key is a user identity_id
|
|
280
|
+
# otherwise key is a user identity_id or identity_name
|
|
281
281
|
return key
|
|
282
282
|
|
|
283
283
|
def _register(self):
|
|
@@ -374,7 +374,7 @@ class BackendStore(PersistenceStore):
|
|
|
374
374
|
if not user:
|
|
375
375
|
return
|
|
376
376
|
|
|
377
|
-
user_identifier = user.identity_id
|
|
377
|
+
user_identifier = user.identity_id or user.identity_name
|
|
378
378
|
return await self._notify_user(user_identifier, value=value, ignore_channel=ignore_channel)
|
|
379
379
|
|
|
380
380
|
async def _notify_patches(self, patches: List[Dict[str, Any]]):
|
|
@@ -393,7 +393,7 @@ class BackendStore(PersistenceStore):
|
|
|
393
393
|
if not user:
|
|
394
394
|
return
|
|
395
395
|
|
|
396
|
-
user_identifier = user.identity_id
|
|
396
|
+
user_identifier = user.identity_id or user.identity_name
|
|
397
397
|
return await self._notify_user(user_identifier, patches=patches)
|
|
398
398
|
|
|
399
399
|
async def init(self, variable: 'Variable'):
|
|
@@ -540,25 +540,3 @@ class BackendStoreEntry(BaseModel):
|
|
|
540
540
|
uid: str
|
|
541
541
|
store: BackendStore
|
|
542
542
|
"""Store instance"""
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
class BrowserStore(PersistenceStore):
|
|
546
|
-
"""
|
|
547
|
-
Persistence store implementation that uses browser local storage
|
|
548
|
-
"""
|
|
549
|
-
|
|
550
|
-
async def init(self, variable: 'Variable'):
|
|
551
|
-
# noop
|
|
552
|
-
pass
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
class QueryParamStore(PersistenceStore):
|
|
556
|
-
"""
|
|
557
|
-
Persistence store implementation that uses a URL query parameter
|
|
558
|
-
"""
|
|
559
|
-
|
|
560
|
-
query: str
|
|
561
|
-
|
|
562
|
-
async def init(self, variable: 'Variable'):
|
|
563
|
-
# noop
|
|
564
|
-
pass
|