Skip to content

Commit 4151198

Browse files
Merge branch 'is4481/upgrade-libs' into is4481/upgrade-services
2 parents 3cb4ea5 + 638a0be commit 4151198

File tree

15 files changed

+95
-83
lines changed

15 files changed

+95
-83
lines changed

packages/aws-library/src/aws_library/s3/_client.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from boto3.s3.transfer import TransferConfig
1414
from botocore import exceptions as botocore_exc
1515
from botocore.client import Config
16+
from common_library.pydantic_type_adapters import AnyUrlLegacyAdapter
1617
from models_library.api_schemas_storage import ETag, S3BucketName, UploadedPart
1718
from models_library.basic_types import SHA256Str
1819
from pydantic import AnyUrl, ByteSize, TypeAdapter
@@ -43,8 +44,8 @@
4344
_MAX_CONCURRENT_COPY: Final[int] = 4
4445
_AWS_MAX_ITEMS_PER_PAGE: Final[int] = 1000
4546

46-
_ANY_URL_ADAPTER: Final[TypeAdapter[AnyUrl]] = TypeAdapter(AnyUrl)
47-
_LIST_ANY_URL_ADAPTER: Final[TypeAdapter[list[AnyUrl]]] = TypeAdapter(list[AnyUrl])
47+
48+
ListAnyUrlTypeAdapter: Final[TypeAdapter[list[AnyUrl]]] = TypeAdapter(list[AnyUrl])
4849

4950

5051
class UploadedBytesTransferredCallback(Protocol):
@@ -254,7 +255,7 @@ async def create_single_presigned_download_link(
254255
bucket: S3BucketName,
255256
object_key: S3ObjectKey,
256257
expiration_secs: int,
257-
) -> AnyUrl:
258+
) -> str:
258259
# NOTE: ensure the bucket/object exists, this will raise if not
259260
await self._client.head_bucket(Bucket=bucket)
260261
await self._client.head_object(Bucket=bucket, Key=object_key)
@@ -263,20 +264,20 @@ async def create_single_presigned_download_link(
263264
Params={"Bucket": bucket, "Key": object_key},
264265
ExpiresIn=expiration_secs,
265266
)
266-
return _ANY_URL_ADAPTER.validate_python(generated_link)
267+
return f"{AnyUrlLegacyAdapter.validate_python(generated_link)}"
267268

268269
@s3_exception_handler(_logger)
269270
async def create_single_presigned_upload_link(
270271
self, *, bucket: S3BucketName, object_key: S3ObjectKey, expiration_secs: int
271-
) -> AnyUrl:
272+
) -> str:
272273
# NOTE: ensure the bucket/object exists, this will raise if not
273274
await self._client.head_bucket(Bucket=bucket)
274275
generated_link = await self._client.generate_presigned_url(
275276
"put_object",
276277
Params={"Bucket": bucket, "Key": object_key},
277278
ExpiresIn=expiration_secs,
278279
)
279-
return _ANY_URL_ADAPTER.validate_python(generated_link)
280+
return f"{AnyUrlLegacyAdapter.validate_python(generated_link)}"
280281

281282
@s3_exception_handler(_logger)
282283
async def create_multipart_upload_links(
@@ -299,7 +300,7 @@ async def create_multipart_upload_links(
299300
# compute the number of links, based on the announced file size
300301
num_upload_links, chunk_size = compute_num_file_chunks(file_size)
301302
# now create the links
302-
upload_links = _LIST_ANY_URL_ADAPTER.validate_python(
303+
upload_links = ListAnyUrlTypeAdapter.validate_python(
303304
await asyncio.gather(
304305
*(
305306
self._client.generate_presigned_url(
@@ -473,6 +474,6 @@ def is_multipart(file_size: ByteSize) -> bool:
473474

474475
@staticmethod
475476
def compute_s3_url(*, bucket: S3BucketName, object_key: S3ObjectKey) -> AnyUrl:
476-
return _ANY_URL_ADAPTER.validate_python(
477+
return AnyUrlLegacyAdapter.validate_python(
477478
f"s3://{bucket}/{urllib.parse.quote(object_key)}"
478479
)

packages/aws-library/tests/test_s3_client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -694,11 +694,11 @@ async def test_create_single_presigned_download_link(
694694
object_key=with_uploaded_file_on_s3.s3_key,
695695
expiration_secs=default_expiration_time_seconds,
696696
)
697-
assert isinstance(download_url, AnyUrl)
697+
assert download_url
698698

699699
dest_file = tmp_path / faker.file_name()
700700
async with ClientSession() as session:
701-
response = await session.get(str(download_url))
701+
response = await session.get(download_url)
702702
response.raise_for_status()
703703
with dest_file.open("wb") as fp:
704704
fp.write(await response.read())
@@ -744,7 +744,7 @@ async def test_create_single_presigned_upload_link(
744744
create_file_of_size: Callable[[ByteSize], Path],
745745
default_expiration_time_seconds: int,
746746
upload_to_presigned_link: Callable[
747-
[Path, AnyUrl, S3BucketName, S3ObjectKey], Awaitable[None]
747+
[Path, str, S3BucketName, S3ObjectKey], Awaitable[None]
748748
],
749749
):
750750
file = create_file_of_size(_BYTE_SIZE_ADAPTER.validate_python("1Mib"))
Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
from typing import Annotated, TypeAlias
22

3-
from pydantic import AfterValidator, AnyHttpUrl, HttpUrl
3+
from pydantic import AfterValidator, AnyHttpUrl, AnyUrl, HttpUrl
4+
from pydantic_core import Url
45

56

6-
def _strip_last_slash(url: str) -> str:
7-
return url.rstrip("/")
7+
def _strip_last_slash(url: Url) -> str:
8+
return f"{url}".rstrip("/")
89

910

11+
AnyUrlLegacy: TypeAlias = Annotated[
12+
AnyUrl,
13+
AfterValidator(_strip_last_slash),
14+
]
15+
1016
AnyHttpUrlLegacy: TypeAlias = Annotated[
11-
str, AnyHttpUrl, AfterValidator(_strip_last_slash)
17+
AnyHttpUrl,
18+
AfterValidator(_strip_last_slash),
1219
]
1320

1421

15-
HttpUrlLegacy: TypeAlias = Annotated[str, HttpUrl, AfterValidator(_strip_last_slash)]
22+
HttpUrlLegacy: TypeAlias = Annotated[
23+
HttpUrl,
24+
AfterValidator(_strip_last_slash),
25+
]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from typing import Final
2+
3+
from common_library.pydantic_networks_extension import AnyHttpUrlLegacy, AnyUrlLegacy
4+
from pydantic import TypeAdapter
5+
6+
AnyUrlLegacyAdapter: Final[TypeAdapter[AnyUrlLegacy]] = TypeAdapter(AnyUrlLegacy)
7+
8+
AnyHttpUrlLegacyAdapter: Final[TypeAdapter[AnyHttpUrlLegacy]] = TypeAdapter(AnyHttpUrlLegacy)

packages/common-library/tests/test_pydantic_fields_extension.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Literal
1+
from typing import Any, Callable, Literal
22

33
import pytest
44
from common_library.pydantic_fields_extension import get_type, is_literal, is_nullable
@@ -68,5 +68,5 @@ class MyModel(BaseModel):
6868
(is_nullable, False, "e"),
6969
],
7070
)
71-
def test_field_fn(fn, expected, name):
71+
def test_field_fn(fn: Callable[[Any], Any], expected: Any, name: str):
7272
assert expected == fn(MyModel.model_fields[name])
Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,39 @@
1+
import pytest
12
from common_library.pydantic_networks_extension import AnyHttpUrlLegacy
2-
from pydantic import AnyHttpUrl, TypeAdapter
3+
from pydantic import AnyHttpUrl, BaseModel, TypeAdapter, ValidationError
34
from pydantic_core import Url
45

56

7+
class A(BaseModel):
8+
url: AnyHttpUrlLegacy
9+
10+
611
def test_any_http_url():
712
url = TypeAdapter(AnyHttpUrl).validate_python(
813
"http://backgroud.testserver.io",
914
)
1015

1116
assert isinstance(url, Url)
12-
assert f"{url}" == "http://backgroud.testserver.io/" # NOTE: trailing '/' added in Pydantic v2
17+
assert (
18+
f"{url}" == "http://backgroud.testserver.io/"
19+
) # trailing slash added (in Pydantic v2)
20+
1321

1422
def test_any_http_url_legacy():
1523
url = TypeAdapter(AnyHttpUrlLegacy).validate_python(
16-
"http://backgroud.testserver.io",
24+
"http://backgroud.testserver.io",
1725
)
1826

1927
assert isinstance(url, str)
20-
assert url == "http://backgroud.testserver.io"
28+
assert url == "http://backgroud.testserver.io" # no trailing slash was added
29+
30+
31+
def test_valid_any_http_url_legacy_field():
32+
a = A(url="http://backgroud.testserver.io") # type: ignore
33+
34+
assert a.url == "http://backgroud.testserver.io" # no trailing slash was added
35+
36+
37+
def test_not_valid_any_http_url_legacy_field():
38+
with pytest.raises(ValidationError):
39+
A(url="htttttp://backgroud.testserver.io") # type: ignore

packages/common-library/tests/test_serialization.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from typing import Final
2-
31
import pytest
42
from common_library.serialization import model_dump_with_secrets
53
from pydantic import BaseModel, SecretStr
@@ -10,9 +8,6 @@ class Credentials(BaseModel):
108
PASSWORD: SecretStr | None = None
119

1210

13-
ME: Final[Credentials] = Credentials(USERNAME="DeepThought", PASSWORD=SecretStr("42"))
14-
15-
1611
@pytest.mark.parametrize(
1712
"expected,show_secrets",
1813
[
@@ -27,4 +22,4 @@ class Credentials(BaseModel):
2722
],
2823
)
2924
def test_model_dump_with_secrets(expected: dict, show_secrets: bool):
30-
assert expected == model_dump_with_secrets(ME, show_secrets=show_secrets)
25+
assert expected == model_dump_with_secrets(Credentials(USERNAME="DeepThought", PASSWORD=SecretStr("42")), show_secrets=show_secrets)

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

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

99
from pathlib import Path
10-
from typing import Annotated, Final, TypeAlias
10+
from typing import Annotated, TypeAlias
1111
from uuid import UUID
1212

1313
from models_library.basic_types import ConstrainedStr, KeyIDStr
@@ -45,9 +45,6 @@
4545
]
4646

4747

48-
_ANY_URL_ADAPTER: Final[TypeAdapter[AnyUrl]] = TypeAdapter(AnyUrl)
49-
50-
5148
class SimcoreS3DirectoryID(ConstrainedStr):
5249
"""
5350
A simcore directory has the following structure:
@@ -126,7 +123,7 @@ class DownloadLink(BaseModel):
126123
"""I/O port type to hold a generic download link to a file (e.g. S3 pre-signed link, etc)"""
127124

128125
download_link: Annotated[
129-
str, BeforeValidator(lambda x: str(_ANY_URL_ADAPTER.validate_python(x)))
126+
str, BeforeValidator(lambda x: str(TypeAdapter(AnyUrl).validate_python(x)))
130127
] = Field(..., alias="downloadLink")
131128
label: str | None = Field(default=None, description="Display name")
132129
model_config = ConfigDict(
@@ -177,10 +174,9 @@ def legacy_enforce_str_to_int(cls, v):
177174
return v
178175

179176
model_config = ConfigDict(
180-
populate_by_name=True
177+
populate_by_name=True,
181178
)
182179

183-
184180
class SimCoreFileLink(BaseFileLink):
185181
"""I/O port type to hold a link to a file in simcore S3 storage"""
186182

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import Annotated, Final, Generic, TypeAlias, TypeVar
22

3+
from common_library.pydantic_type_adapters import AnyHttpUrlLegacyAdapter
34
from pydantic import (
4-
AnyHttpUrl,
55
BaseModel,
66
BeforeValidator,
77
ConfigDict,
@@ -15,8 +15,6 @@
1515

1616
from .utils.common_validators import none_to_empty_list_pre_validator
1717

18-
_ANY_HTTP_URL_ADAPTER: Final[TypeAdapter[AnyHttpUrl]] = TypeAdapter(AnyHttpUrl)
19-
2018
# Default limit values
2119
# - Using same values across all pagination entrypoints simplifies
2220
# interconnecting paginated calls
@@ -101,7 +99,7 @@ class PageLinks(
10199
PageRefs[
102100
Annotated[
103101
str,
104-
BeforeValidator(lambda x: str(_ANY_HTTP_URL_ADAPTER.validate_python(x))),
102+
BeforeValidator(lambda x: str(AnyHttpUrlLegacyAdapter.validate_python(x))),
105103
]
106104
]
107105
):

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

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from math import ceil
22
from typing import Any, Protocol, TypedDict, Union, runtime_checkable
33

4-
from pydantic import AnyHttpUrl, TypeAdapter
4+
from common_library.pydantic_type_adapters import AnyHttpUrlLegacyAdapter
55

66
from .rest_pagination import PageLinks, PageMetaInfoLimitOffset
77

@@ -29,7 +29,6 @@ def replace_query_params(self, **kwargs: Any) -> "_StarletteURL":
2929

3030

3131
_URLType = Union[_YarlURL, _StarletteURL]
32-
_ANY_HTTP_URL_ADAPTER: TypeAdapter = TypeAdapter(AnyHttpUrl)
3332

3433

3534
def _replace_query(url: _URLType, query: dict[str, Any]) -> str:
@@ -39,7 +38,9 @@ def _replace_query(url: _URLType, query: dict[str, Any]) -> str:
3938
new_url = url.update_query(query)
4039
else:
4140
new_url = url.replace_query_params(**query)
42-
return f"{new_url}"
41+
42+
new_url_str = f"{new_url}"
43+
return f"{AnyHttpUrlLegacyAdapter.validate_python(new_url_str)}"
4344

4445

4546
class PageDict(TypedDict):
@@ -72,33 +73,21 @@ def paginate_data(
7273
total=total, count=len(chunk), limit=limit, offset=offset
7374
),
7475
_links=PageLinks(
75-
self=(
76-
_ANY_HTTP_URL_ADAPTER.validate_python(
77-
_replace_query(request_url, {"offset": offset, "limit": limit}),
78-
)
79-
),
80-
first=_ANY_HTTP_URL_ADAPTER.validate_python(
81-
_replace_query(request_url, {"offset": 0, "limit": limit})
82-
),
83-
prev=_ANY_HTTP_URL_ADAPTER.validate_python(
84-
_replace_query(
85-
request_url, {"offset": max(offset - limit, 0), "limit": limit}
86-
),
76+
self=_replace_query(request_url, {"offset": offset, "limit": limit}),
77+
first=_replace_query(request_url, {"offset": 0, "limit": limit}),
78+
prev=_replace_query(
79+
request_url, {"offset": max(offset - limit, 0), "limit": limit}
8780
)
8881
if offset > 0
8982
else None,
90-
next=_ANY_HTTP_URL_ADAPTER.validate_python(
91-
_replace_query(
92-
request_url,
93-
{"offset": min(offset + limit, last_page * limit), "limit": limit},
94-
),
83+
next=_replace_query(
84+
request_url,
85+
{"offset": min(offset + limit, last_page * limit), "limit": limit},
9586
)
9687
if offset < (last_page * limit)
9788
else None,
98-
last=_ANY_HTTP_URL_ADAPTER.validate_python(
99-
_replace_query(
100-
request_url, {"offset": last_page * limit, "limit": limit}
101-
),
89+
last=_replace_query(
90+
request_url, {"offset": last_page * limit, "limit": limit}
10291
),
10392
),
10493
data=chunk,

0 commit comments

Comments
 (0)