Skip to content

Commit 0e21209

Browse files
authored
Merge branch 'master' into pr-osparc-ooil-escape-additional-env-vars
2 parents 6f7f276 + 289f936 commit 0e21209

File tree

69 files changed

+2195
-331
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+2195
-331
lines changed

packages/models-library/src/models_library/api_schemas_directorv2/computations.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
from ..basic_types import IDStr
1515
from ..projects import ProjectID
16-
from ..projects_nodes_io import NodeID
16+
from ..projects_nodes_io import NodeID, SimcoreS3FileID
1717
from ..projects_pipeline import ComputationTask
1818
from ..users import UserID
1919
from ..wallets import WalletInfo
@@ -126,6 +126,30 @@ class TaskLogFileGet(BaseModel):
126126
] = None
127127

128128

129+
class TaskLogFileIdGet(BaseModel):
130+
task_id: NodeID
131+
file_id: SimcoreS3FileID | None
132+
133+
model_config = ConfigDict(
134+
json_schema_extra={
135+
"examples": [
136+
{
137+
"task_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
138+
"file_id": "1c46752c-b096-11ea-a3c4-02420a00392e/3fa85f64-5717-4562-b3fc-2c963f66afa6/logs/task_logs.txt",
139+
},
140+
{
141+
"task_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
142+
"file_id": "1c46752c-b096-11ea-a3c4-02420a00392e/6ba7b810-9dad-11d1-80b4-00c04fd430c8/logs/debug.log",
143+
},
144+
{
145+
"task_id": "6ba7b811-9dad-11d1-80b4-00c04fd430c8",
146+
"file_id": None,
147+
},
148+
]
149+
}
150+
)
151+
152+
129153
class TasksSelection(BaseModel):
130154
nodes_ids: list[NodeID]
131155

packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ class AsyncJobResult(BaseModel):
3030

3131

3232
class AsyncJobGet(BaseModel):
33+
model_config = ConfigDict(
34+
json_schema_extra={
35+
"examples": [
36+
{
37+
"job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
38+
"job_name": "export_data_task",
39+
}
40+
]
41+
}
42+
)
43+
3344
job_id: AsyncJobId
3445
job_name: AsyncJobName
3546

@@ -42,6 +53,18 @@ class AsyncJobAbort(BaseModel):
4253
class AsyncJobFilter(AsyncJobFilterBase):
4354
"""Data for controlling access to an async job"""
4455

56+
model_config = ConfigDict(
57+
json_schema_extra={
58+
"examples": [
59+
{
60+
"product_name": "osparc",
61+
"user_id": 123,
62+
"client_name": "web_client",
63+
}
64+
]
65+
},
66+
)
67+
4568
product_name: ProductName
4669
user_id: UserID
4770
client_name: Annotated[
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# pylint: disable=no-self-use
2+
# pylint: disable=not-context-manager
3+
# pylint: disable=protected-access
4+
# pylint: disable=redefined-outer-name
5+
# pylint: disable=unused-argument
6+
# pylint: disable=unused-variable
7+
8+
9+
from models_library.api_schemas_directorv2.computations import TaskLogFileIdGet
10+
from models_library.projects import ProjectID
11+
from pydantic import TypeAdapter, validate_call
12+
from pytest_mock import MockType
13+
from servicelib.rabbitmq._client_rpc import RabbitMQRPCClient
14+
15+
16+
class DirectorV2SideEffects:
17+
# pylint: disable=no-self-use
18+
@validate_call(config={"arbitrary_types_allowed": True})
19+
async def get_computation_task_log_file_ids(
20+
self,
21+
rpc_client: RabbitMQRPCClient | MockType,
22+
*,
23+
project_id: ProjectID,
24+
) -> list[TaskLogFileIdGet]:
25+
assert rpc_client
26+
assert project_id
27+
28+
return TypeAdapter(list[TaskLogFileIdGet]).validate_python(
29+
TaskLogFileIdGet.model_json_schema()["examples"],
30+
)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# pylint: disable=no-self-use
2+
# pylint: disable=not-context-manager
3+
# pylint: disable=protected-access
4+
# pylint: disable=redefined-outer-name
5+
# pylint: disable=unused-argument
6+
# pylint: disable=unused-variable
7+
8+
9+
from typing import Literal
10+
11+
from models_library.api_schemas_rpc_async_jobs.async_jobs import (
12+
AsyncJobFilter,
13+
AsyncJobGet,
14+
)
15+
from models_library.api_schemas_webserver.storage import PathToExport
16+
from models_library.products import ProductName
17+
from models_library.users import UserID
18+
from pydantic import TypeAdapter, validate_call
19+
from pytest_mock import MockType
20+
from servicelib.rabbitmq._client_rpc import RabbitMQRPCClient
21+
22+
23+
class StorageSideEffects:
24+
# pylint: disable=no-self-use
25+
@validate_call(config={"arbitrary_types_allowed": True})
26+
async def start_export_data(
27+
self,
28+
rabbitmq_rpc_client: RabbitMQRPCClient | MockType,
29+
*,
30+
user_id: UserID,
31+
product_name: ProductName,
32+
paths_to_export: list[PathToExport],
33+
export_as: Literal["path", "download_link"],
34+
) -> tuple[AsyncJobGet, AsyncJobFilter]:
35+
assert rabbitmq_rpc_client
36+
assert user_id
37+
assert product_name
38+
assert paths_to_export
39+
assert export_as
40+
41+
async_job_get = TypeAdapter(AsyncJobGet).validate_python(
42+
AsyncJobGet.model_json_schema()["examples"][0],
43+
)
44+
async_job_filter = TypeAdapter(AsyncJobFilter).validate_python(
45+
AsyncJobFilter.model_json_schema()["examples"][0],
46+
)
47+
48+
return async_job_get, async_job_filter
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# pylint: disable=too-many-arguments
2+
import logging
3+
from typing import Final
4+
5+
from models_library.api_schemas_directorv2 import (
6+
DIRECTOR_V2_RPC_NAMESPACE,
7+
)
8+
from models_library.api_schemas_directorv2.computations import TaskLogFileIdGet
9+
from models_library.projects import ProjectID
10+
from models_library.rabbitmq_basic_types import RPCMethodName
11+
from pydantic import TypeAdapter
12+
13+
from ....logging_utils import log_decorator
14+
from ... import RabbitMQRPCClient
15+
16+
_logger = logging.getLogger(__name__)
17+
18+
19+
_RPC_METHOD_NAME_ADAPTER: TypeAdapter[RPCMethodName] = TypeAdapter(RPCMethodName)
20+
21+
_GET_COMPUTATION_TASK_LOG_FILE_IDS: Final[RPCMethodName] = (
22+
_RPC_METHOD_NAME_ADAPTER.validate_python("get_computation_task_log_file_ids")
23+
)
24+
25+
26+
@log_decorator(_logger, level=logging.DEBUG)
27+
async def get_computation_task_log_file_ids(
28+
rabbitmq_rpc_client: RabbitMQRPCClient, *, project_id: ProjectID
29+
) -> list[TaskLogFileIdGet]:
30+
"""
31+
Raises:
32+
ComputationalTaskMissingError
33+
"""
34+
result = await rabbitmq_rpc_client.request(
35+
DIRECTOR_V2_RPC_NAMESPACE,
36+
_GET_COMPUTATION_TASK_LOG_FILE_IDS,
37+
project_id=project_id,
38+
)
39+
assert isinstance(result, list) # nosec
40+
assert all(isinstance(item, TaskLogFileIdGet) for item in result) # nosec
41+
return result
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from ..._errors import RPCInterfaceError
2+
3+
4+
class BaseRpcError(RPCInterfaceError):
5+
pass
6+
7+
8+
class ComputationalTaskMissingError(BaseRpcError):
9+
msg_template = "Computational run not found for project {project_id}"

packages/service-library/src/servicelib/utils_meta.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
""" Utilities to implement _meta.py
2-
3-
"""
1+
"""Utilities to implement _meta.py"""
42

3+
import re
54
from importlib.metadata import distribution
65

76
from models_library.basic_types import VersionStr
87
from packaging.version import Version
98
from pydantic import TypeAdapter
109

10+
_APP_NAME_PATTERN = re.compile(
11+
r"^[a-z0-9]+(-[a-z0-9]+)*$"
12+
) # matches lowercase string with words and non-negative integers separated by dashes (no whitespace)
13+
1114

1215
class PackageInfo:
1316
"""Thin wrapper around pgk_resources.Distribution to access package distribution metadata
@@ -29,11 +32,31 @@ def __init__(self, package_name: str):
2932
package_name: as defined in 'setup.name'
3033
"""
3134
self._distribution = distribution(package_name)
35+
# property checks
36+
if re.match(_APP_NAME_PATTERN, self.app_name) is None:
37+
raise ValueError(
38+
f"Invalid package name {self.app_name}. "
39+
"It must be all lowercase and words separated by dashes ('-')."
40+
)
3241

3342
@property
3443
def project_name(self) -> str:
3544
return self._distribution.metadata["Name"]
3645

46+
@property
47+
def app_name(self) -> str:
48+
"""
49+
Returns the application name as a lowercase string with words separated by dashes ('-').
50+
"""
51+
return self._distribution.metadata["Name"]
52+
53+
@property
54+
def prometheus_friendly_app_name(self) -> str:
55+
"""
56+
Returns a version of the app name which is compatible with Prometheus metrics naming conventions (no dashes).
57+
"""
58+
return self.app_name.replace("-", "_")
59+
3760
@property
3861
def version(self) -> Version:
3962
return Version(self._distribution.version)

packages/service-library/tests/test_utils_meta.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from typing import Final
22

3+
import pytest
34
from models_library.basic_types import VersionStr
45
from packaging.version import Version
6+
from pytest_mock import MockerFixture
57
from servicelib.utils_meta import PackageInfo
68

79

@@ -32,3 +34,33 @@ def test_meta_module_implementation():
3234

3335
assert __version__ in APP_FINISHED_BANNER_MSG
3436
assert PROJECT_NAME in APP_FINISHED_BANNER_MSG
37+
38+
39+
@pytest.mark.parametrize(
40+
"package_name, app_name, is_valid_app_name, is_correct_app_name",
41+
[
42+
("simcore-service-library", "simcore-service-library", True, True),
43+
("simcore-service-lib", "simcore-service-library", True, False),
44+
("simcore_service_library", "simcore_service_library", False, True),
45+
],
46+
)
47+
def test_app_name(
48+
mocker: MockerFixture,
49+
package_name: str,
50+
app_name: str,
51+
is_valid_app_name: bool,
52+
is_correct_app_name: bool,
53+
):
54+
55+
def mock_distribution(name):
56+
return mocker.Mock(metadata={"Name": name, "Version": "1.0.0"})
57+
58+
mocker.patch("servicelib.utils_meta.distribution", side_effect=mock_distribution)
59+
if is_valid_app_name:
60+
info = PackageInfo(package_name=package_name)
61+
if is_correct_app_name:
62+
assert info.app_name == app_name
63+
assert info.prometheus_friendly_app_name == app_name.replace("-", "_")
64+
else:
65+
with pytest.raises(ValueError):
66+
_ = PackageInfo(package_name=package_name)

services/api-server/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.9.2
1+
0.10.0

0 commit comments

Comments
 (0)