Skip to content

Commit e89806f

Browse files
⚠️🎨✨ Execute the api-server's run and map function api endpoints in celery worker (#8233)
forced upon request
1 parent 8768457 commit e89806f

File tree

54 files changed

+2421
-484
lines changed

Some content is hidden

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

54 files changed

+2421
-484
lines changed

.github/prompts/update-user-messages.prompt.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
mode: 'edit'
33
description: 'Update user messages'
4+
model: Claude Sonnet 3.5
45
---
56

67
This prompt guide is for updating user-facing messages in ${file} or ${selection}
@@ -43,7 +44,17 @@ When modifying user messages, follow **as close as possible** these rules:
4344
user_message("Unable to load project.", _version=1)
4445
```
4546

46-
3. **Message Style**: Follow **strictly** the guidelines in `${workspaceFolder}/docs/user-messages-guidelines.md`
47+
3. **Message Style**: Follow **STRICTLY ALL 10 GUIDELINES** in `${workspaceFolder}/docs/user-messages-guidelines.md`:
48+
- Be Clear and Concise
49+
- Provide Specific and Actionable Information
50+
- Avoid Technical Jargon
51+
- Use a Polite and Non-Blaming Tone
52+
- Avoid Negative Words and Phrases
53+
- Place Messages Appropriately
54+
- Use Inline Validation When Possible
55+
- Avoid Using All-Caps and Excessive Punctuation
56+
- **Use Humor Sparingly** - Avoid casual phrases like "Oops!", "Whoops!", or overly informal language
57+
- Offer Alternative Solutions or Support
4758

4859
4. **Preserve Context**: Ensure the modified message conveys the same meaning and context as the original.
4960

@@ -56,8 +67,10 @@ When modifying user messages, follow **as close as possible** these rules:
5667
# After
5768
user_message("Your session has expired. Please log in again.", _version=3)
5869
```
70+
5971
6. **Replace 'Study' by 'Project'**: If the message contains the word 'Study', replace it with 'Project' to align with our terminology.
6072

73+
7. **Professional Tone**: Maintain a professional, helpful tone. Avoid humor, casual expressions, or overly informal language that might not be appropriate for all users or situations.
6174

6275
## Examples
6376

@@ -91,4 +104,14 @@ return HttpErrorInfo(status.HTTP_404_NOT_FOUND, user_message("User not found.",
91104
return HttpErrorInfo(status.HTTP_404_NOT_FOUND, user_message("The requested user could not be found.", _version=2))
92105
```
93106

94-
Remember: The goal is to improve clarity and helpfulness for end-users while maintaining accurate versioning for tracking changes.
107+
### Example 4: Removing Humor (Guideline 9)
108+
109+
```python
110+
# Before
111+
user_message("Oops! Something went wrong, but we've noted it down and we'll sort it out ASAP. Thanks for your patience!")
112+
113+
# After
114+
user_message("Something went wrong on our end. We've been notified and will resolve this issue as soon as possible. Thank you for your patience.", _version=1)
115+
```
116+
117+
Remember: The goal is to improve clarity and helpfulness for end-users while maintaining accurate versioning for tracking changes. **Always check that your updated messages comply with ALL 10 guidelines, especially avoiding humor and maintaining a professional tone.**

packages/celery-library/tests/unit/test_tasks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
from random import randint
1212

1313
import pytest
14-
from celery import Celery, Task
15-
from celery.contrib.abortable import AbortableTask
14+
from celery import Celery, Task # pylint: disable=no-name-in-module
15+
from celery.contrib.abortable import AbortableTask # pylint: disable=no-name-in-module
1616
from celery_library.errors import TransferrableCeleryError
1717
from celery_library.task import register_task
1818
from celery_library.task_manager import CeleryTaskManager

packages/models-library/src/models_library/api_schemas_long_running_tasks/base.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
from collections.abc import Awaitable, Callable
33
from typing import Annotated, TypeAlias
44

5-
from pydantic import BaseModel, Field, field_validator, validate_call
5+
from pydantic import BaseModel, ConfigDict, Field, field_validator, validate_call
6+
from pydantic.config import JsonDict
67

78
_logger = logging.getLogger(__name__)
89

@@ -23,6 +24,22 @@ class TaskProgress(BaseModel):
2324
message: ProgressMessage = ""
2425
percent: ProgressPercent = 0.0
2526

27+
@staticmethod
28+
def _update_json_schema_extra(schema: JsonDict) -> None:
29+
schema.update(
30+
{
31+
"examples": [
32+
{
33+
"task_id": "3ac48b54-a48d-4c5e-a6ac-dcaddb9eaa59",
34+
"message": "Halfway done",
35+
"percent": 0.5,
36+
}
37+
]
38+
}
39+
)
40+
41+
model_config = ConfigDict(json_schema_extra=_update_json_schema_extra)
42+
2643
# used to propagate progress updates internally
2744
_update_callback: Callable[["TaskProgress"], Awaitable[None]] | None = None
2845

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

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import datetime
22
from typing import Annotated, TypeAlias
33

4-
from pydantic import Field, HttpUrl
4+
from pydantic import ConfigDict, Field, HttpUrl
55

66
from ..functions import (
77
Function,
@@ -141,6 +141,45 @@ class RegisteredProjectFunctionGet(RegisteredProjectFunction, OutputSchema):
141141
modified_at: Annotated[datetime.datetime, Field(alias="lastChangeDate")]
142142
access_rights: dict[GroupID, FunctionGroupAccessRightsGet]
143143
thumbnail: HttpUrl | None = None
144+
model_config = ConfigDict(
145+
populate_by_name=True,
146+
json_schema_extra={
147+
"examples": [
148+
{
149+
"function_class": "PROJECT",
150+
"title": "Example Project Function",
151+
"description": "This is an example project function.",
152+
"input_schema": {
153+
"schema_content": {
154+
"type": "object",
155+
"properties": {"input1": {"type": "integer"}},
156+
},
157+
"schema_class": "application/schema+json",
158+
},
159+
"output_schema": {
160+
"schema_content": {
161+
"type": "object",
162+
"properties": {"output1": {"type": "string"}},
163+
},
164+
"schema_class": "application/schema+json",
165+
},
166+
"default_inputs": None,
167+
"project_id": "11111111-1111-1111-1111-111111111111",
168+
"uid": "22222222-2222-2222-2222-222222222222",
169+
"created_at": "2024-01-01T12:00:00",
170+
"modified_at": "2024-01-02T12:00:00",
171+
"access_rights": {
172+
"5": {
173+
"read": True,
174+
"write": False,
175+
"execute": True,
176+
}
177+
},
178+
"thumbnail": None,
179+
},
180+
]
181+
},
182+
)
144183

145184

146185
class SolverFunctionToRegister(SolverFunction, InputSchema): ...

packages/models-library/src/models_library/functions.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,37 @@ class ProjectFunction(FunctionBase):
114114

115115

116116
class RegisteredProjectFunction(ProjectFunction, RegisteredFunctionBase):
117-
pass
117+
model_config = ConfigDict(
118+
populate_by_name=True,
119+
json_schema_extra={
120+
"examples": [
121+
{
122+
"function_class": "PROJECT",
123+
"title": "Example Project Function",
124+
"description": "This is an example project function.",
125+
"input_schema": {
126+
"schema_content": {
127+
"type": "object",
128+
"properties": {"input1": {"type": "integer"}},
129+
},
130+
"schema_class": "application/schema+json",
131+
},
132+
"output_schema": {
133+
"schema_content": {
134+
"type": "object",
135+
"properties": {"output1": {"type": "string"}},
136+
},
137+
"schema_class": "application/schema+json",
138+
},
139+
"default_inputs": None,
140+
"project_id": "11111111-1111-1111-1111-111111111111",
141+
"uid": "22222222-2222-2222-2222-222222222222",
142+
"created_at": "2024-01-01T12:00:00",
143+
"modified_at": "2024-01-02T12:00:00",
144+
},
145+
]
146+
},
147+
)
118148

119149

120150
SolverJobID: TypeAlias = UUID

packages/models-library/src/models_library/progress_bar.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Literal, TypeAlias
22

33
from pydantic import BaseModel, ConfigDict
4+
from pydantic.config import JsonDict
45

56
# NOTE: keep a list of possible unit, and please use correct official unit names
67
ProgressUnit: TypeAlias = Literal["Byte"]
@@ -13,9 +14,10 @@ class ProgressStructuredMessage(BaseModel):
1314
unit: str | None = None
1415
sub: "ProgressStructuredMessage | None" = None
1516

16-
model_config = ConfigDict(
17-
json_schema_extra={
18-
"examples": [
17+
@staticmethod
18+
def _update_json_schema_extra(schema: JsonDict) -> None:
19+
schema.update(
20+
examples=[
1921
{
2022
"description": "some description",
2123
"current": 12.2,
@@ -39,8 +41,9 @@ class ProgressStructuredMessage(BaseModel):
3941
},
4042
},
4143
]
42-
}
43-
)
44+
)
45+
46+
model_config = ConfigDict(json_schema_extra=_update_json_schema_extra)
4447

4548

4649
UNITLESS = None
@@ -96,7 +99,17 @@ def composed_message(self) -> str:
9699
{
97100
"actual_value": 0.3,
98101
"total": 1.0,
99-
"message": ProgressStructuredMessage.model_config["json_schema_extra"]["examples"][2], # type: ignore [index]
102+
"message": {
103+
"description": "downloading",
104+
"current": 2.0,
105+
"total": 5,
106+
"sub": {
107+
"description": "port 2",
108+
"current": 12.2,
109+
"total": 123,
110+
"unit": "Byte",
111+
},
112+
},
100113
},
101114
]
102115
},
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# pylint: disable=redefined-outer-name
2+
3+
from collections.abc import Callable
4+
5+
import pytest
6+
from faker import Faker
7+
from pytest_mock import MockerFixture, MockType
8+
from servicelib.celery.models import TaskStatus, TaskUUID
9+
from servicelib.celery.task_manager import Task, TaskManager
10+
11+
_faker = Faker()
12+
13+
14+
@pytest.fixture
15+
def submit_task_return_value() -> TaskUUID:
16+
return TaskUUID(_faker.uuid4())
17+
18+
19+
@pytest.fixture
20+
def cancel_task_return_value() -> None:
21+
return None
22+
23+
24+
@pytest.fixture
25+
def get_task_result_return_value() -> dict:
26+
return {"result": "example"}
27+
28+
29+
@pytest.fixture
30+
def get_task_status_return_value() -> TaskStatus:
31+
example = TaskStatus.model_json_schema()["examples"][0]
32+
return TaskStatus.model_validate(example)
33+
34+
35+
@pytest.fixture
36+
def list_tasks_return_value() -> list[Task]:
37+
examples = Task.model_json_schema()["examples"]
38+
assert len(examples) > 0
39+
return [Task.model_validate(example) for example in examples]
40+
41+
42+
@pytest.fixture
43+
def set_task_progress_return_value() -> None:
44+
return None
45+
46+
47+
@pytest.fixture
48+
def mock_task_manager_object(
49+
mocker: MockerFixture,
50+
submit_task_return_value: TaskUUID,
51+
cancel_task_return_value: None,
52+
get_task_result_return_value: dict,
53+
get_task_status_return_value: TaskStatus,
54+
list_tasks_return_value: list[Task],
55+
set_task_progress_return_value: None,
56+
) -> MockType:
57+
"""
58+
Returns a TaskManager mock with overridable return values for each method.
59+
If a return value is an Exception, the method will raise it.
60+
"""
61+
mock = mocker.Mock(spec=TaskManager)
62+
63+
def _set_return_or_raise(method, value):
64+
if isinstance(value, Exception):
65+
method.side_effect = lambda *a, **kw: (_ for _ in ()).throw(value)
66+
else:
67+
method.return_value = value
68+
69+
_set_return_or_raise(mock.submit_task, submit_task_return_value)
70+
_set_return_or_raise(mock.cancel_task, cancel_task_return_value)
71+
_set_return_or_raise(mock.get_task_result, get_task_result_return_value)
72+
_set_return_or_raise(mock.get_task_status, get_task_status_return_value)
73+
_set_return_or_raise(mock.list_tasks, list_tasks_return_value)
74+
_set_return_or_raise(mock.set_task_progress, set_task_progress_return_value)
75+
return mock
76+
77+
78+
@pytest.fixture
79+
def mock_task_manager_object_raising_factory(
80+
mocker: MockerFixture,
81+
) -> Callable[[Exception], MockType]:
82+
def _factory(task_manager_exception: Exception) -> MockType:
83+
mock = mocker.Mock(spec=TaskManager)
84+
85+
def _raise_exc(*args, **kwargs):
86+
raise task_manager_exception
87+
88+
mock.submit_task.side_effect = _raise_exc
89+
mock.cancel_task.side_effect = _raise_exc
90+
mock.get_task_result.side_effect = _raise_exc
91+
mock.get_task_status.side_effect = _raise_exc
92+
mock.list_tasks.side_effect = _raise_exc
93+
mock.set_task_progress.side_effect = _raise_exc
94+
return mock
95+
96+
return _factory

packages/pytest-simcore/src/pytest_simcore/simcore_services.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030

3131
_SERVICES_TO_SKIP: Final[set[str]] = {
32+
"api-worker",
3233
"agent", # global mode deploy (NO exposed ports, has http API)
3334
"dask-sidecar", # global mode deploy (NO exposed ports, **NO** http API)
3435
"migration",

0 commit comments

Comments
 (0)