Skip to content

Commit 2e752e4

Browse files
author
Andrei Neagu
committed
Merge remote-tracking branch 'upstream/master' into pr-osparc-long-running-refactor-2
2 parents 61daf54 + 5cc2bdd commit 2e752e4

File tree

28 files changed

+405
-216
lines changed

28 files changed

+405
-216
lines changed

.github/copilot-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,4 @@ This document provides guidelines and best practices for using GitHub Copilot in
5252
- [Python Coding Conventions](../docs/coding-conventions.md)
5353
- [Environment Variables Guide](../docs/env-vars.md)
5454
- [Steps to Upgrade Python](../docs/steps-to-upgrade-python.md)
55-
- [Node.js Installation Script](../scripts/install_nodejs_14.bash)
55+
- [Pydantic Annotated fields](../docs/llm-prompts/pydantic-annotated-fields.md)

.vscode/settings.template.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// This is a template. Clone and replace extension ".template.json" by ".json"
22
{
33
"autoDocstring.docstringFormat": "pep257",
4-
54
"editor.tabSize": 2,
65
"editor.insertSpaces": true,
76
"editor.detectIndentation": false,
@@ -34,6 +33,8 @@
3433
"python.analysis.typeCheckingMode": "basic",
3534
"python.analysis.extraPaths": [
3635
"./packages/aws-library/src",
36+
"./packages/common-library/src",
37+
"./packages/dask-task-models-library/src",
3738
"./packages/models-library/src",
3839
"./packages/postgres-database/src",
3940
"./packages/postgres-database/tests",

packages/models-library/src/models_library/api_schemas_webserver/products.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,23 @@ class ProductUIGet(OutputSchema):
135135
ExtraCreditsUsdRangeInt: TypeAlias = Annotated[int, Field(ge=0, lt=500)]
136136

137137

138+
TrialAccountAnnotated: TypeAlias = Annotated[
139+
PositiveInt | None,
140+
Field(
141+
description="Expiration time in days for trial accounts; `null` means not a trial account"
142+
),
143+
]
144+
145+
WelcomeCreditsAnnotated: TypeAlias = Annotated[
146+
ExtraCreditsUsdRangeInt | None,
147+
Field(description="Welcome credits in USD; `null` means no welcome credits"),
148+
]
149+
150+
138151
class InvitationGenerate(InputSchema):
139152
guest: LowerCaseEmailStr
140-
trial_account_days: PositiveInt | None = None
141-
extra_credits_in_usd: ExtraCreditsUsdRangeInt | None = None
153+
trial_account_days: TrialAccountAnnotated = None
154+
extra_credits_in_usd: WelcomeCreditsAnnotated = None
142155

143156

144157
class InvitationGenerated(OutputSchema):

packages/models-library/src/models_library/api_schemas_webserver/users.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
OutputSchemaWithoutCamelCase,
4242
)
4343
from .groups import MyGroupsGet
44-
from .products import InvitationGenerate
44+
from .products import TrialAccountAnnotated, WelcomeCreditsAnnotated
4545
from .users_preferences import AggregatedPreferences
4646

4747
#
@@ -255,9 +255,14 @@ class UsersForAdminListFilter(Filters):
255255
class UsersAccountListQueryParams(UsersForAdminListFilter, PageQueryParameters): ...
256256

257257

258+
class _InvitationDetails(InputSchema):
259+
trial_account_days: TrialAccountAnnotated = None
260+
extra_credits_in_usd: WelcomeCreditsAnnotated = None
261+
262+
258263
class UserAccountApprove(InputSchema):
259264
email: EmailStr
260-
invitation: InvitationGenerate | None = None
265+
invitation: _InvitationDetails | None = None
261266

262267

263268
class UserAccountReject(InputSchema):

services/dask-sidecar/src/simcore_service_dask_sidecar/computational_sidecar/core.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from pathlib import Path
88
from pprint import pformat
99
from types import TracebackType
10-
from typing import Final, cast
10+
from typing import cast
1111
from uuid import uuid4
1212

1313
from aiodocker import Docker
@@ -32,6 +32,7 @@
3232
from ..utils.dask import TaskPublisher
3333
from ..utils.files import (
3434
check_need_unzipping,
35+
log_partial_file_content,
3536
pull_file_from_remote,
3637
push_file_to_remote,
3738
)
@@ -48,8 +49,8 @@
4849
from .task_shared_volume import TaskSharedVolumes
4950

5051
_logger = logging.getLogger(__name__)
51-
CONTAINER_WAIT_TIME_SECS = 2
52-
_TASK_PROCESSING_PROGRESS_WEIGHT: Final[float] = 0.99
52+
_CONTAINER_WAIT_TIME_SECS = 2
53+
_MAX_LOGGED_FILE_CHARS = 40
5354

5455

5556
@dataclass(kw_only=True, frozen=True, slots=True)
@@ -147,11 +148,17 @@ async def _retrieve_output_data(
147148
upload_tasks = []
148149
for output_params in output_data.values():
149150
if isinstance(output_params, FileUrl):
150-
assert ( # nosec
151+
assert (
151152
output_params.file_mapping
152153
), f"{output_params.model_dump_json(indent=1)} expected resolved in TaskOutputData.from_task_output"
153154

154155
src_path = task_volumes.outputs_folder / output_params.file_mapping
156+
await log_partial_file_content(
157+
src_path,
158+
logger=_logger,
159+
log_level=logging.DEBUG,
160+
max_chars=_MAX_LOGGED_FILE_CHARS,
161+
)
155162
upload_tasks.append(
156163
push_file_to_remote(
157164
src_path,
@@ -267,7 +274,7 @@ async def run(self, command: list[str]) -> TaskOutputData:
267274
)
268275
# wait until the container finished, either success or fail or timeout
269276
while (container_data := await container.show())["State"]["Running"]:
270-
await asyncio.sleep(CONTAINER_WAIT_TIME_SECS)
277+
await asyncio.sleep(_CONTAINER_WAIT_TIME_SECS)
271278
if container_data["State"]["ExitCode"] > os.EX_OK:
272279
raise ServiceRuntimeError(
273280
service_key=self.task_parameters.image,

services/dask-sidecar/src/simcore_service_dask_sidecar/utils/files.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from settings_library.s3 import S3Settings
2121
from yarl import URL
2222

23-
logger = logging.getLogger(__name__)
23+
_logger = logging.getLogger(__name__)
2424

2525
HTTP_FILE_SYSTEM_SCHEMES: Final = ["http", "https"]
2626
S3_FILE_SYSTEM_SCHEMES: Final = ["s3", "s3a"]
@@ -208,7 +208,7 @@ async def pull_file_from_remote(
208208
await log_publishing_cb(
209209
f"Uncompressing '{download_dst_path.name}'...", logging.INFO
210210
)
211-
logger.debug(
211+
_logger.debug(
212212
"%s is a zip file and will be now uncompressed", download_dst_path
213213
)
214214
with repro_zipfile.ReproducibleZipFile(download_dst_path, "r") as zip_obj:
@@ -258,7 +258,7 @@ async def _push_file_to_remote(
258258
log_publishing_cb: LogPublishingCB,
259259
s3_settings: S3Settings | None,
260260
) -> None:
261-
logger.debug("Uploading %s to %s...", file_to_upload, dst_url)
261+
_logger.debug("Uploading %s to %s...", file_to_upload, dst_url)
262262
assert dst_url.path # nosec
263263

264264
storage_kwargs: S3FsSettingsDict | dict[str, Any] = {}
@@ -306,7 +306,7 @@ async def push_file_to_remote(
306306
await asyncio.get_event_loop().run_in_executor(
307307
None, zfp.write, src_path, src_path.name
308308
)
309-
logger.debug("%s created.", archive_file_path)
309+
_logger.debug("%s created.", archive_file_path)
310310
assert archive_file_path.exists() # nosec
311311
file_to_upload = archive_file_path
312312
await log_publishing_cb(
@@ -319,7 +319,7 @@ async def push_file_to_remote(
319319
)
320320

321321
if dst_url.scheme in HTTP_FILE_SYSTEM_SCHEMES:
322-
logger.debug("destination is a http presigned link")
322+
_logger.debug("destination is a http presigned link")
323323
await _push_file_to_http_link(file_to_upload, dst_url, log_publishing_cb)
324324
else:
325325
await _push_file_to_remote(
@@ -330,3 +330,22 @@ async def push_file_to_remote(
330330
f"Upload of '{src_path.name}' to '{dst_url.path.strip('/')}' complete",
331331
logging.INFO,
332332
)
333+
334+
335+
async def log_partial_file_content(
336+
src_path: Path, *, logger: logging.Logger, log_level: int, max_chars: int
337+
) -> None:
338+
if max_chars < 0:
339+
msg = "max_chars must be non-negative"
340+
raise ValueError(msg)
341+
if max_chars == 0:
342+
return
343+
if not src_path.exists():
344+
logger.log(log_level, "file does not exist: %s", src_path)
345+
return
346+
async with aiofiles.open(src_path, encoding="utf-8") as f:
347+
content = await f.read(max_chars + 1)
348+
if len(content) > max_chars:
349+
logger.log(log_level, "file content (truncated): %s...", content[:max_chars])
350+
else:
351+
logger.log(log_level, "file content: %s", content)

services/dask-sidecar/tests/unit/test_utils_files.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import asyncio
66
import hashlib
7+
import logging
78
import mimetypes
89
import zipfile
910
from collections.abc import AsyncIterable
@@ -21,6 +22,7 @@
2122
from settings_library.s3 import S3Settings
2223
from simcore_service_dask_sidecar.utils.files import (
2324
_s3fs_settings_from_s3_settings,
25+
log_partial_file_content,
2426
pull_file_from_remote,
2527
push_file_to_remote,
2628
)
@@ -511,3 +513,48 @@ async def test_push_file_to_remote_creates_reproducible_zip_archive(
511513
assert dst_path2.exists()
512514

513515
assert _compute_hash(dst_path1) == _compute_hash(dst_path2)
516+
517+
518+
async def test_log_partial_file_content(
519+
tmp_path: Path, caplog: pytest.LogCaptureFixture
520+
):
521+
# Create a file with known content
522+
file_content = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
523+
file_path = tmp_path / "testfile.txt"
524+
file_path.write_text(file_content)
525+
logger = logging.getLogger("pytest.utils.files")
526+
527+
# Case 1: file longer than max_chars
528+
with caplog.at_level(logging.DEBUG, logger=logger.name):
529+
await log_partial_file_content(
530+
file_path, logger=logger, log_level=logging.DEBUG, max_chars=10
531+
)
532+
assert any(
533+
"file content (truncated): abcdefghij..." in record.getMessage()
534+
for record in caplog.records
535+
)
536+
537+
# Case 2: file shorter than max_chars
538+
caplog.clear()
539+
short_content = "short"
540+
short_file = tmp_path / "short.txt"
541+
short_file.write_text(short_content)
542+
with caplog.at_level(logging.DEBUG, logger=logger.name):
543+
await log_partial_file_content(
544+
short_file, logger=logger, log_level=logging.DEBUG, max_chars=10
545+
)
546+
assert any(
547+
"file content: short" in record.getMessage() for record in caplog.records
548+
)
549+
550+
# Case 3: file does not exist
551+
caplog.clear()
552+
non_existent = tmp_path / "doesnotexist.txt"
553+
with caplog.at_level(logging.DEBUG, logger=logger.name):
554+
await log_partial_file_content(
555+
non_existent, logger=logger, log_level=logging.DEBUG, max_chars=10
556+
)
557+
assert any(
558+
f"file does not exist: {non_existent}" in record.getMessage()
559+
for record in caplog.records
560+
)

services/docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,6 +1249,7 @@ services:
12491249
hostname: "sto-worker-{{.Node.Hostname}}-{{.Task.Slot}}"
12501250
environment:
12511251
<<: *storage_environment
1252+
STORAGE_WORKER_NAME: "sto-worker-{{.Node.Hostname}}-{{.Task.Slot}}-{{.Task.ID}}"
12521253
STORAGE_WORKER_MODE: "true"
12531254
CELERY_CONCURRENCY: 100
12541255
networks: *storage_networks
@@ -1259,6 +1260,7 @@ services:
12591260
hostname: "sto-worker-cpu-bound-{{.Node.Hostname}}-{{.Task.Slot}}"
12601261
environment:
12611262
<<: *storage_environment
1263+
STORAGE_WORKER_NAME: "sto-worker-cpu-bound-{{.Node.Hostname}}-{{.Task.Slot}}-{{.Task.ID}}"
12621264
STORAGE_WORKER_MODE: "true"
12631265
CELERY_CONCURRENCY: 1
12641266
CELERY_QUEUES: "cpu_bound"

services/static-webserver/client/compile.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@
136136
"class": "osparc.Application",
137137
"theme": "osparc.theme.products.tis.ThemeDark",
138138
"name": "tis",
139-
"title": "TIP V3.0 - IT'IS",
139+
"title": "TIP V4.0 - IT'IS",
140140
"include": [
141141
"iconfont.material.Load",
142142
"iconfont.fontawesome5.Load",

services/static-webserver/client/source/class/osparc/desktop/MainPage.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ qx.Class.define("osparc.desktop.MainPage", {
115115
const preferencesSettings = osparc.Preferences.getInstance();
116116
if (!isReadOnly && preferencesSettings.getConfirmBackToDashboard()) {
117117
const studyName = this.__studyEditor.getStudy().getName();
118-
const win = new osparc.ui.window.Confirmation();
118+
const win = new osparc.ui.window.Confirmation().set({
119+
confirmAction: "warning",
120+
});
119121
if (osparc.product.Utils.getProductName().includes("s4l")) {
120122
let msg = this.tr("Do you want to close ") + "<b>" + studyName + "</b>?";
121123
msg += "<br><br>";

0 commit comments

Comments
 (0)