Skip to content

Commit 174fb88

Browse files
committed
Merge branch 'master' into add-functions-locust-test
2 parents 9903871 + 8362d80 commit 174fb88

File tree

97 files changed

+1028
-299
lines changed

Some content is hidden

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

97 files changed

+1028
-299
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Prompt
2+
3+
```
4+
Please convert all pydantic model fields that use `Field()` with default values to use the Annotated pattern instead.
5+
Follow these guidelines:
6+
7+
1. Move default values outside of `Field()` like this: `field_name: Annotated[field_type, Field(description="")] = default_value`.
8+
2. Keep all other parameters like validation_alias and descriptions inside `Field()`.
9+
3. For fields using default_factory, keep that parameter as is in the `Field()` constructor, but set the default value outside to DEFAULT_FACTORY from common_library.basic_types. Example: `field_name: Annotated[dict_type, Field(default_factory=dict)] = DEFAULT_FACTORY`.
10+
4. Add the import: `from common_library.basic_types import DEFAULT_FACTORY` if it's not already present.
11+
5. If `Field()` has no parameters (empty), don't use Annotated at all. Just use: `field_name: field_type = default_value`.
12+
6. Leave any model validations, `model_config` settings, and `field_validators` untouched.
13+
```
14+
## Examples
15+
16+
### Before:
17+
18+
```python
19+
from pydantic import BaseModel, Field
20+
21+
class UserModel(BaseModel):
22+
name: str = Field(default="Anonymous", description="User's display name")
23+
age: int = Field(default=18, ge=0, lt=120)
24+
tags: list[str] = Field(default_factory=list, description="User tags")
25+
metadata: dict[str, str] = Field(default_factory=dict)
26+
is_active: bool = Field(default=True)
27+
```
28+
29+
- **After**
30+
31+
```python
32+
from typing import Annotated
33+
from pydantic import BaseModel, Field
34+
from common_library.basic_types import DEFAULT_FACTORY
35+
36+
class UserModel(BaseModel):
37+
name: Annotated[str, Field(description="User's display name")] = "Anonymous"
38+
age: Annotated[int, Field(ge=0, lt=120)] = 18
39+
tags: Annotated[list[str], Field(default_factory=list, description="User tags")] = DEFAULT_FACTORY
40+
metadata: Annotated[dict[str, str], Field(default_factory=dict)] = DEFAULT_FACTORY
41+
is_active: bool = True
42+
```
43+
44+
## Another Example with Complex Fields
45+
46+
### Before:
47+
48+
```python
49+
from pydantic import BaseModel, Field, field_validator
50+
from datetime import datetime
51+
52+
class ProjectModel(BaseModel):
53+
id: str = Field(default_factory=uuid.uuid4, description="Unique project identifier")
54+
name: str = Field(default="Untitled Project", min_length=3, max_length=50)
55+
created_at: datetime = Field(default_factory=datetime.now)
56+
config: dict = Field(default={"version": "1.0", "theme": "default"})
57+
58+
@field_validator("name")
59+
def validate_name(cls, v):
60+
if v.isdigit():
61+
raise ValueError("Name cannot be only digits")
62+
return v
63+
```
64+
65+
### After:
66+
67+
```python
68+
from typing import Annotated
69+
from pydantic import BaseModel, Field, field_validator
70+
from datetime import datetime
71+
from common_library.basic_types import DEFAULT_FACTORY
72+
73+
class ProjectModel(BaseModel):
74+
id: Annotated[str, Field(default_factory=uuid.uuid4, description="Unique project identifier")] = DEFAULT_FACTORY
75+
name: Annotated[str, Field(min_length=3, max_length=50)] = "Untitled Project"
76+
created_at: Annotated[datetime, Field(default_factory=datetime.now)] = DEFAULT_FACTORY
77+
config: dict = {"version": "1.0", "theme": "default"}
78+
79+
@field_validator("name")
80+
def validate_name(cls, v):
81+
if v.isdigit():
82+
raise ValueError("Name cannot be only digits")
83+
return v
84+
```

packages/aws-library/requirements/_base.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ opentelemetry-api==1.30.0
145145
# opentelemetry-exporter-otlp-proto-grpc
146146
# opentelemetry-exporter-otlp-proto-http
147147
# opentelemetry-instrumentation
148+
# opentelemetry-instrumentation-aio-pika
148149
# opentelemetry-instrumentation-botocore
149150
# opentelemetry-instrumentation-logging
150151
# opentelemetry-instrumentation-redis
@@ -164,10 +165,13 @@ opentelemetry-exporter-otlp-proto-http==1.30.0
164165
# via opentelemetry-exporter-otlp
165166
opentelemetry-instrumentation==0.51b0
166167
# via
168+
# opentelemetry-instrumentation-aio-pika
167169
# opentelemetry-instrumentation-botocore
168170
# opentelemetry-instrumentation-logging
169171
# opentelemetry-instrumentation-redis
170172
# opentelemetry-instrumentation-requests
173+
opentelemetry-instrumentation-aio-pika==0.51b0
174+
# via -r requirements/../../../packages/service-library/requirements/_base.in
171175
opentelemetry-instrumentation-botocore==0.51b0
172176
# via -r requirements/_base.in
173177
opentelemetry-instrumentation-logging==0.51b0
@@ -431,6 +435,7 @@ wrapt==1.17.2
431435
# aiobotocore
432436
# deprecated
433437
# opentelemetry-instrumentation
438+
# opentelemetry-instrumentation-aio-pika
434439
# opentelemetry-instrumentation-redis
435440
yarl==1.18.3
436441
# via

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,16 @@ class WalletGetPermissions(WalletGet):
7474
)
7575

7676

77-
class CreateWalletBodyParams(OutputSchema):
77+
class CreateWalletBodyParams(InputSchema):
7878
name: str
7979
description: str | None = None
8080
thumbnail: str | None = None
8181

8282

83-
class PutWalletBodyParams(OutputSchema):
83+
class PutWalletBodyParams(InputSchema):
8484
name: str
8585
description: str | None
86-
thumbnail: str | None
86+
thumbnail: str | None = None
8787
status: WalletStatus
8888

8989

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

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from re import Pattern
44
from typing import Annotated, ClassVar, Final, TypeAlias
55

6+
import annotated_types
67
from common_library.basic_types import BootModeEnum, BuildTargetEnum, LogLevel
78
from pydantic import Field, HttpUrl, PositiveInt, StringConstraints
89
from pydantic_core import core_schema
@@ -13,15 +14,16 @@
1314
SIMPLE_VERSION_RE,
1415
UUID_RE,
1516
)
17+
from .utils.common_validators import trim_string_before
1618

1719
assert issubclass(LogLevel, Enum) # nosec
1820
assert issubclass(BootModeEnum, Enum) # nosec
1921
assert issubclass(BuildTargetEnum, Enum) # nosec
2022

2123
__all__: tuple[str, ...] = (
22-
"LogLevel",
2324
"BootModeEnum",
2425
"BuildTargetEnum",
26+
"LogLevel",
2527
)
2628

2729

@@ -70,12 +72,31 @@
7072
UUIDStr: TypeAlias = Annotated[str, StringConstraints(pattern=UUID_RE)]
7173

7274

75+
SafeQueryStr: TypeAlias = Annotated[
76+
str,
77+
StringConstraints(
78+
max_length=512, # Reasonable limit for query parameters to avoid overflows
79+
strip_whitespace=True,
80+
),
81+
annotated_types.doc(
82+
"""
83+
A string that is safe to use in URLs and query parameters.
84+
""",
85+
),
86+
]
87+
88+
7389
# non-empty bounded string used as identifier
7490
# e.g. "123" or "name_123" or "fa327c73-52d8-462a-9267-84eeaf0f90e3" but NOT ""
7591
_ELLIPSIS_CHAR: Final[str] = "..."
7692

7793

7894
class ConstrainedStr(str):
95+
"""Emulates pydantic's v1 constrained types
96+
97+
DEPRECATED: Use instead Annotated[str, StringConstraints(...)]
98+
"""
99+
79100
pattern: str | Pattern[str] | None = None
80101
min_length: int | None = None
81102
max_length: int | None = None
@@ -102,6 +123,11 @@ def __get_pydantic_core_schema__(cls, _source_type, _handler):
102123

103124

104125
class IDStr(ConstrainedStr):
126+
"""Non-empty bounded string used as identifier
127+
128+
DEPRECATED: Use instead Annotated[str, StringConstraints(strip_whitespace=True, min_length=1, max_length=100)]
129+
"""
130+
105131
strip_whitespace = True
106132
min_length = 1
107133
max_length = 100
@@ -125,21 +151,36 @@ def concatenate(*args: "IDStr", link_char: str = " ") -> "IDStr":
125151
return IDStr(result)
126152

127153

128-
class ShortTruncatedStr(ConstrainedStr):
129-
# NOTE: Use to input e.g. titles or display names
130-
# A truncated string:
131-
# - Strips whitespaces and truncate strings that exceed the specified characters limit (curtail_length).
132-
# - Ensures that the **input** data length to the API is controlled and prevents exceeding large inputs silently, i.e. without raising errors.
133-
# SEE https://github.com/ITISFoundation/osparc-simcore/pull/5989#discussion_r1650506583
134-
strip_whitespace = True
135-
curtail_length = 600
136-
154+
_SHORT_TRUNCATED_STR_MAX_LENGTH: Final[int] = 600
155+
ShortTruncatedStr: TypeAlias = Annotated[
156+
str,
157+
StringConstraints(strip_whitespace=True),
158+
trim_string_before(max_length=_SHORT_TRUNCATED_STR_MAX_LENGTH),
159+
annotated_types.doc(
160+
"""
161+
A truncated string used to input e.g. titles or display names.
162+
Strips whitespaces and truncate strings that exceed the specified characters limit (curtail_length).
163+
Ensures that the **input** data length to the API is controlled and prevents exceeding large inputs silently,
164+
i.e. without raising errors.
165+
"""
166+
# SEE https://github.com/ITISFoundation/osparc-simcore/pull/5989#discussion_r1650506583
167+
),
168+
]
137169

138-
class LongTruncatedStr(ConstrainedStr):
139-
# NOTE: Use to input e.g. descriptions or summaries
140-
# Analogous to ShortTruncatedStr
141-
strip_whitespace = True
142-
curtail_length = 65536 # same as github descripton
170+
_LONG_TRUNCATED_STR_MAX_LENGTH: Final[int] = 65536 # same as github description
171+
LongTruncatedStr: TypeAlias = Annotated[
172+
str,
173+
StringConstraints(strip_whitespace=True),
174+
trim_string_before(max_length=_LONG_TRUNCATED_STR_MAX_LENGTH),
175+
annotated_types.doc(
176+
"""
177+
A truncated string used to input e.g. descriptions or summaries.
178+
Strips whitespaces and truncate strings that exceed the specified characters limit (curtail_length).
179+
Ensures that the **input** data length to the API is controlled and prevents exceeding large inputs silently,
180+
i.e. without raising errors.
181+
"""
182+
),
183+
]
143184

144185

145186
# auto-incremented primary-key IDs

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@
1111
from models_library.basic_types import ConstrainedStr
1212
from models_library.folders import FolderID
1313
from models_library.workspaces import WorkspaceID
14-
from pydantic import BaseModel, ConfigDict, Field, HttpUrl, field_validator
14+
from pydantic import (
15+
BaseModel,
16+
ConfigDict,
17+
Field,
18+
HttpUrl,
19+
StringConstraints,
20+
field_validator,
21+
)
1522

1623
from .basic_regex import DATE_RE, UUID_RE_BASE
1724
from .emails import LowerCaseEmailStr
@@ -35,8 +42,7 @@
3542
_DATETIME_FORMAT: Final[str] = "%Y-%m-%dT%H:%M:%S.%fZ"
3643

3744

38-
class ProjectIDStr(ConstrainedStr):
39-
pattern = UUID_RE_BASE
45+
ProjectIDStr: TypeAlias = Annotated[str, StringConstraints(pattern=UUID_RE_BASE)]
4046

4147

4248
class DateTimeStr(ConstrainedStr):

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from typing import Final
1+
from typing import Annotated, Final, TypeAlias
22

33
from models_library.basic_types import ConstrainedStr
4-
from pydantic import TypeAdapter
4+
from pydantic import StringConstraints, TypeAdapter
55

66
REGEX_RABBIT_QUEUE_ALLOWED_SYMBOLS: Final[str] = r"^[\w\-\.]*$"
77

@@ -21,7 +21,9 @@ def from_entries(cls, entries: dict[str, str]) -> "RPCNamespace":
2121
return TypeAdapter(cls).validate_python(composed_string)
2222

2323

24-
class RPCMethodName(ConstrainedStr):
25-
pattern = REGEX_RABBIT_QUEUE_ALLOWED_SYMBOLS
26-
min_length: int = 1
27-
max_length: int = 252
24+
RPCMethodName: TypeAlias = Annotated[
25+
str,
26+
StringConstraints(
27+
pattern=REGEX_RABBIT_QUEUE_ALLOWED_SYMBOLS, min_length=1, max_length=252
28+
),
29+
]

packages/models-library/tests/test_basic_types.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import pytest
44
from models_library.basic_types import (
5+
_SHORT_TRUNCATED_STR_MAX_LENGTH,
56
EnvVarKey,
67
IDStr,
78
MD5Str,
@@ -76,16 +77,28 @@ def test_string_identifier_constraint_type():
7677

7778

7879
def test_short_truncated_string():
80+
curtail_length = _SHORT_TRUNCATED_STR_MAX_LENGTH
7981
assert (
80-
TypeAdapter(ShortTruncatedStr).validate_python(
81-
"X" * ShortTruncatedStr.curtail_length
82-
)
83-
== "X" * ShortTruncatedStr.curtail_length
84-
)
82+
TypeAdapter(ShortTruncatedStr).validate_python("X" * curtail_length)
83+
== "X" * curtail_length
84+
), "Max length string should remain intact"
8585

8686
assert (
87-
TypeAdapter(ShortTruncatedStr).validate_python(
88-
"X" * (ShortTruncatedStr.curtail_length + 1)
89-
)
90-
== "X" * ShortTruncatedStr.curtail_length
91-
)
87+
TypeAdapter(ShortTruncatedStr).validate_python("X" * (curtail_length + 1))
88+
== "X" * curtail_length
89+
), "Overlong string should be truncated exactly to max length"
90+
91+
assert (
92+
TypeAdapter(ShortTruncatedStr).validate_python("X" * (curtail_length + 100))
93+
== "X" * curtail_length
94+
), "Much longer string should still truncate to exact max length"
95+
96+
# below limit
97+
assert TypeAdapter(ShortTruncatedStr).validate_python(
98+
"X" * (curtail_length - 1)
99+
) == "X" * (curtail_length - 1), "Under-length string should not be modified"
100+
101+
# spaces are trimmed
102+
assert (
103+
TypeAdapter(ShortTruncatedStr).validate_python(" " * (curtail_length + 1)) == ""
104+
), "Only-whitespace string should become empty string"

packages/models-library/tests/test_projects.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

88
import pytest
99
from faker import Faker
10-
from models_library.api_schemas_webserver.projects import LongTruncatedStr, ProjectPatch
10+
from models_library.api_schemas_webserver.projects import ProjectPatch
11+
from models_library.basic_types import _LONG_TRUNCATED_STR_MAX_LENGTH
1112
from models_library.projects import Project
1213

1314

@@ -47,8 +48,7 @@ def test_project_with_thumbnail_as_empty_string(minimal_project: dict[str, Any])
4748

4849
def test_project_patch_truncates_description():
4950
# NOTE: checks https://github.com/ITISFoundation/osparc-simcore/issues/5988
50-
assert LongTruncatedStr.curtail_length
51-
len_truncated = int(LongTruncatedStr.curtail_length)
51+
len_truncated = _LONG_TRUNCATED_STR_MAX_LENGTH
5252

5353
long_description = "X" * (len_truncated + 10)
5454
assert len(long_description) > len_truncated

packages/notifications-library/src/notifications_library/_models.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,13 @@ class SharerData:
3030

3131
@dataclass(frozen=True)
3232
class ProductUIData:
33-
logo_url: str
34-
strong_color: str
3533
project_alias: str
34+
logo_url: str | None = (
35+
None # default_logo = "https://raw.githubusercontent.com/ITISFoundation/osparc-simcore/refs/heads/master/services/static-webserver/client/source/resource/osparc/osparc-white.svg" in base.html
36+
)
37+
strong_color: str | None = (
38+
None # default_strong_color = "rgb(131, 0, 191)" in base.html
39+
)
3640

3741

3842
@dataclass(frozen=True)
@@ -41,5 +45,5 @@ class ProductData:
4145
display_name: str
4246
vendor_display_inline: str
4347
support_email: str
44-
homepage_url: str
48+
homepage_url: str | None # default_homepage = "https://osparc.io/" in base.html
4549
ui: ProductUIData

0 commit comments

Comments
 (0)