Skip to content

Commit 84d1b30

Browse files
Merge branch 'master' into add-conversation-messages-ws-events
2 parents b98ab22 + 3ad6b04 commit 84d1b30

File tree

154 files changed

+2150
-939
lines changed

Some content is hidden

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

154 files changed

+2150
-939
lines changed

.github/copilot-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,5 @@ This document provides guidelines and best practices for using GitHub Copilot in
5151

5252
- [Python Coding Conventions](../docs/coding-conventions.md)
5353
- [Environment Variables Guide](../docs/env-vars.md)
54-
- [Steps to Upgrade Python](../docs/steps-to-upgrade-python.md)
5554
- [Pydantic Annotated fields](../docs/llm-prompts/pydantic-annotated-fields.md)
55+
- [Steps to Upgrade Python](../docs/steps-to-upgrade-python.md)
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
mode: 'edit'
3+
description: 'Update user messages'
4+
---
5+
6+
This prompt guide is for updating user-facing messages in ${file} or ${selection}
7+
8+
## What is a User Message?
9+
10+
A user message is any string that will be displayed to end-users of the application.
11+
In our codebase, these messages are marked with the `user_message` function:
12+
13+
```python
14+
from common_library.user_messages import user_message
15+
16+
error_msg = user_message("Operation failed. Please try again later.")
17+
```
18+
19+
## Guidelines for Updating User Messages
20+
21+
When modifying user messages, follow these rules:
22+
23+
1. **Version Tracking**: Every modification to a user message must include an incremented `_version` parameter:
24+
25+
```python
26+
# Before modification
27+
user_message("Error: Unable to connect to the server.")
28+
29+
# After modification, add _version or increment it if it already exists
30+
user_message("Currently unable to establish connection to the server.", _version=1)
31+
```
32+
33+
2. **F-String Preservation**: When modifying messages that use f-strings, preserve all parameters and their formatting:
34+
35+
```python
36+
# Before
37+
user_message(f"Project {project_name} could not be loaded.")
38+
39+
# After (correct)
40+
user_message(f"Unable to load project {project_name}.", _version=1)
41+
42+
# After (incorrect - lost the parameter)
43+
user_message("Unable to load project.", _version=1)
44+
```
45+
46+
3. **Message Style**: Follow *strictly* the guidelines in `${workspaceFolder}/docs/user-messages-guidelines.md`
47+
48+
4. **Preserve Context**: Ensure the modified message conveys the same meaning and context as the original.
49+
50+
5. **Incremental Versioning**: If a message already has a version, increment it by 1:
51+
52+
```python
53+
# Before
54+
user_message("Session expired.", _version=2)
55+
56+
# After
57+
user_message("Your session has expired. Please log in again.", _version=3)
58+
```
59+
60+
## Examples
61+
62+
### Example 1: Simple Message Update
63+
64+
```python
65+
# Before
66+
error_dialog(user_message("Failed to save changes."))
67+
68+
# After
69+
error_dialog(user_message("Unable to save your changes. Please try again.", _version=1))
70+
```
71+
72+
### Example 2: F-string Message Update
73+
74+
```python
75+
# Before
76+
raise ValueError(user_message(f"Invalid input parameter: {param_name}"))
77+
78+
# After
79+
raise ValueError(user_message(f"The parameter '{param_name}' contains a value that is not allowed.", _version=1))
80+
```
81+
82+
### Example 3: Already Versioned Message
83+
84+
```python
85+
# Before
86+
return HttpErrorInfo(status.HTTP_404_NOT_FOUND, user_message("User not found.", _version=1))
87+
88+
# After
89+
return HttpErrorInfo(status.HTTP_404_NOT_FOUND, user_message("The requested user could not be found.", _version=2))
90+
```
91+
92+
Remember: The goal is to improve clarity and helpfulness for end-users while maintaining accurate versioning for tracking changes.

.vscode/launch.template.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,28 @@
4444
"justMyCode": false
4545
},
4646
{
47+
// This test adds --external-envfile and expects a file named ".secrets" in the workspace root.
48+
"name": "Python: Test w/ repo.config",
49+
"type": "debugpy",
50+
"request": "launch",
51+
"module": "pytest",
52+
"args": [
53+
"--ff",
54+
"--log-cli-level=INFO",
55+
"--external-envfile=${workspaceFolder}/.secrets",
56+
"--pdb",
57+
"--setup-show",
58+
"--durations=5",
59+
"-s",
60+
"-vv",
61+
"${file}"
62+
],
63+
"cwd": "${workspaceFolder}",
64+
"console": "integratedTerminal",
65+
"justMyCode": false
66+
},
67+
{
68+
// This tests enables the httpx spy and dumps captures in a json. Mainly for api-server
4769
"name": "Python: Test-Httpx-Spy",
4870
"type": "debugpy",
4971
"request": "launch",

docs/messages-guidelines.md renamed to docs/user-messages-guidelines.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Error and Warning Message Guidelines
1+
# User Message Guidelines
22

3-
These guidelines ensure that messages are user-friendly, clear, and helpful while maintaining a professional tone. 🚀
3+
These guidelines ensure that error and warnings user-facing messages are user-friendly, clear, and helpful while maintaining a professional tone. 🚀
44

55
Some details:
66

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
def user_message(msg: str, *, _version: int | None = None) -> str:
2+
"""Marks a message as user-facing
3+
4+
Arguments:
5+
msg -- human-friendly string that follows docs/user-messages-guidelines.md
6+
_version -- version number to track changes to messages; increment when modifying an existing message
7+
8+
Returns:
9+
The original message string, allowing it to be used inline in code
10+
"""
11+
return msg
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from common_library.user_messages import user_message
2+
3+
4+
def test_user_message() -> None:
5+
6+
assert user_message("This is a user message") == "This is a user message"

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

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88
from uuid import UUID
99

1010
from common_library.basic_types import DEFAULT_FACTORY
11-
from models_library.basic_types import ConstrainedStr
12-
from models_library.folders import FolderID
13-
from models_library.workspaces import WorkspaceID
1411
from pydantic import (
1512
BaseModel,
1613
ConfigDict,
@@ -21,8 +18,11 @@
2118
)
2219

2320
from .basic_regex import DATE_RE, UUID_RE_BASE
21+
from .basic_types import ConstrainedStr
2422
from .emails import LowerCaseEmailStr
23+
from .folders import FolderID
2524
from .groups import GroupID
25+
from .products import ProductName
2626
from .projects_access import AccessRights, GroupIDStr
2727
from .projects_nodes import Node
2828
from .projects_nodes_io import NodeIDStr
@@ -33,6 +33,7 @@
3333
none_to_empty_str_pre_validator,
3434
)
3535
from .utils.enums import StrAutoEnum
36+
from .workspaces import WorkspaceID
3637

3738
ProjectID: TypeAlias = UUID
3839
CommitID: TypeAlias = int
@@ -147,6 +148,25 @@ def _convert_sql_alchemy_enum(cls, v):
147148
)
148149

149150

151+
class ProjectListAtDB(BaseProjectModel):
152+
id: int
153+
type: ProjectType
154+
template_type: ProjectTemplateType | None
155+
prj_owner: int | None
156+
ui: dict[str, Any] | None
157+
classifiers: list[ClassifierID] | None
158+
dev: dict[str, Any] | None
159+
quality: dict[str, Any]
160+
published: bool | None
161+
hidden: bool
162+
workspace_id: WorkspaceID | None
163+
trashed: datetime | None
164+
trashed_by: UserID | None
165+
trashed_explicitly: bool
166+
product_name: ProductName
167+
folder_id: FolderID | None
168+
169+
150170
class Project(BaseProjectModel):
151171
# NOTE: This is the pydantic pendant of project-v0.0.1.json used in the API of the webserver/webclient
152172
# NOT for usage with DB!!

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ class Node(BaseModel):
168168
),
169169
] = None
170170

171-
thumbnail: Annotated[
171+
thumbnail: Annotated[ # <-- (DEPRECATED) Can be removed
172172
str | HttpUrl | None,
173173
Field(
174174
description="url of the latest screenshot of the node",
@@ -232,18 +232,18 @@ class Node(BaseModel):
232232
] = DEFAULT_FACTORY
233233

234234
output_node: Annotated[bool | None, Field(deprecated=True, alias="outputNode")] = (
235-
None
235+
None # <-- (DEPRECATED) Can be removed
236236
)
237237

238-
output_nodes: Annotated[
238+
output_nodes: Annotated[ # <-- (DEPRECATED) Can be removed
239239
list[NodeID] | None,
240240
Field(
241241
description="Used in group-nodes. Node IDs of those connected to the output",
242242
alias="outputNodes",
243243
),
244244
] = None
245245

246-
parent: Annotated[
246+
parent: Annotated[ # <-- (DEPRECATED) Can be removed
247247
NodeID | None,
248248
Field(
249249
description="Parent's (group-nodes') node ID s. Used to group",

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

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# pylint: disable=unused-variable
44

55

6+
import logging
67
import re
78
from pathlib import Path
89
from typing import Any
@@ -12,6 +13,8 @@
1213
from .helpers.monkeypatch_envs import load_dotenv, setenvs_from_dict
1314
from .helpers.typing_env import EnvVarsDict
1415

16+
_logger = logging.getLogger(__name__)
17+
1518

1619
def pytest_addoption(parser: pytest.Parser):
1720
simcore_group = parser.getgroup("simcore")
@@ -20,12 +23,17 @@ def pytest_addoption(parser: pytest.Parser):
2023
action="store",
2124
type=Path,
2225
default=None,
23-
help="Path to an env file. Consider passing a link to repo configs, i.e. `ln -s /path/to/osparc-ops-config/repo.config`",
26+
help="Path to an env file. Replaces .env-devel in the tests by an external envfile."
27+
"e.g. consider "
28+
" `ln -s /path/to/osparc-ops-config/repo.config .secrets` and then "
29+
" `pytest --external-envfile=.secrets --pdb tests/unit/test_core_settings.py`",
2430
)
2531

2632

2733
@pytest.fixture(scope="session")
28-
def external_envfile_dict(request: pytest.FixtureRequest) -> EnvVarsDict:
34+
def external_envfile_dict(
35+
request: pytest.FixtureRequest, osparc_simcore_root_dir: Path
36+
) -> EnvVarsDict:
2937
"""
3038
If a file under test folder prefixed with `.env-secret` is present,
3139
then this fixture captures it.
@@ -35,19 +43,43 @@ def external_envfile_dict(request: pytest.FixtureRequest) -> EnvVarsDict:
3543
"""
3644
envs = {}
3745
if envfile := request.config.getoption("--external-envfile"):
38-
print("🚨 EXTERNAL `envfile` option detected. Loading", envfile, "...")
46+
_logger.warning(
47+
"🚨 EXTERNAL `envfile` option detected. Loading '%s' ...", envfile
48+
)
3949

4050
assert isinstance(envfile, Path)
4151
assert envfile.exists()
4252
assert envfile.is_file()
4353

54+
envfile = envfile.resolve()
55+
osparc_simcore_root_dir = osparc_simcore_root_dir.resolve()
56+
57+
if osparc_simcore_root_dir in envfile.parents and not any(
58+
term in envfile.name.lower() for term in ("ignore", "secret")
59+
):
60+
_logger.warning(
61+
"🚨 CAUTION: The external envfile '%s' may contain sensitive data and could be accidentally versioned. "
62+
"To prevent this, include the words 'secret' or 'ignore' in the filename.",
63+
envfile.name,
64+
)
65+
4466
envs = load_dotenv(envfile)
4567

68+
if envs:
69+
response = input(
70+
f"🚨 CAUTION: You are about to run tests using environment variables loaded from '{envfile}'.\n"
71+
"This may cause tests to interact with or modify real external systems (e.g., production or staging environments).\n"
72+
"Proceeding could result in data loss or unintended side effects.\n"
73+
"Are you sure you want to continue? [y/N]: "
74+
)
75+
if response.strip().lower() not in ("y", "yes"):
76+
pytest.exit("Aborted by user due to external envfile usage.")
77+
4678
return envs
4779

4880

4981
@pytest.fixture(scope="session")
50-
def skip_if_external_envfile_dict(external_envfile_dict: EnvVarsDict) -> None:
82+
def skip_if_no_external_envfile(external_envfile_dict: EnvVarsDict) -> None:
5183
if not external_envfile_dict:
5284
pytest.skip(reason="Skipping test since external-envfile is not set")
5385

0 commit comments

Comments
 (0)