Skip to content

Commit 068c84a

Browse files
authored
Merge branch 'master' into fix/path-size
2 parents c510d6f + c9296d7 commit 068c84a

File tree

60 files changed

+1989
-1337
lines changed

Some content is hidden

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

60 files changed

+1989
-1337
lines changed

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

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -85,21 +85,30 @@ async def create(
8585
cls, settings: S3Settings, s3_max_concurrency: int = _S3_MAX_CONCURRENCY_DEFAULT
8686
) -> "SimcoreS3API":
8787
session = aioboto3.Session()
88-
session_client = session.client( # type: ignore[call-overload]
89-
"s3",
90-
endpoint_url=f"{settings.S3_ENDPOINT}",
91-
aws_access_key_id=settings.S3_ACCESS_KEY,
92-
aws_secret_access_key=settings.S3_SECRET_KEY,
93-
region_name=settings.S3_REGION,
94-
config=Config(signature_version="s3v4"),
95-
)
96-
assert isinstance(session_client, ClientCreatorContext) # nosec
88+
session_client = None
9789
exit_stack = contextlib.AsyncExitStack()
98-
s3_client = cast(S3Client, await exit_stack.enter_async_context(session_client))
99-
# NOTE: this triggers a botocore.exception.ClientError in case the connection is not made to the S3 backend
100-
await s3_client.list_buckets()
90+
try:
91+
session_client = session.client( # type: ignore[call-overload]
92+
"s3",
93+
endpoint_url=f"{settings.S3_ENDPOINT}",
94+
aws_access_key_id=settings.S3_ACCESS_KEY,
95+
aws_secret_access_key=settings.S3_SECRET_KEY,
96+
region_name=settings.S3_REGION,
97+
config=Config(signature_version="s3v4"),
98+
)
99+
assert isinstance(session_client, ClientCreatorContext) # nosec
101100

102-
return cls(s3_client, session, exit_stack, s3_max_concurrency)
101+
s3_client = cast(
102+
S3Client, await exit_stack.enter_async_context(session_client)
103+
)
104+
# NOTE: this triggers a botocore.exception.ClientError in case the connection is not made to the S3 backend
105+
await s3_client.list_buckets()
106+
107+
return cls(s3_client, session, exit_stack, s3_max_concurrency)
108+
except Exception:
109+
await exit_stack.aclose()
110+
111+
raise
103112

104113
async def close(self) -> None:
105114
await self._exit_stack.aclose()

packages/models-library/src/models_library/api_schemas_catalog/services.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,11 +323,20 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
323323
)
324324

325325

326-
PageRpcServicesGetV2: TypeAlias = PageRpc[
326+
PageRpcLatestServiceGet: TypeAlias = PageRpc[
327327
# WARNING: keep this definition in models_library and not in the RPC interface
328+
# otherwise the metaclass PageRpc[*] will create *different* classes in server/client side
329+
# and will fail to serialize/deserialize these parameters when transmitted/received
328330
LatestServiceGet
329331
]
330332

333+
PageRpcServiceRelease: TypeAlias = PageRpc[
334+
# WARNING: keep this definition in models_library and not in the RPC interface
335+
# otherwise the metaclass PageRpc[*] will create *different* classes in server/client side
336+
# and will fail to serialize/deserialize these parameters when transmitted/received
337+
ServiceRelease
338+
]
339+
331340
ServiceResourcesGet: TypeAlias = ServiceResourcesDict
332341

333342

@@ -365,3 +374,6 @@ class MyServiceGet(CatalogOutputSchema):
365374

366375
owner: GroupID | None
367376
my_access_rights: ServiceGroupAccessRightsV2
377+
378+
379+
__all__: tuple[str, ...] = ("ServiceRelease",)

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828

2929
PageOffsetInt: TypeAlias = NonNegativeInt
3030

31+
PageTotalCount: TypeAlias = NonNegativeInt
32+
3133
DEFAULT_NUMBER_OF_ITEMS_PER_PAGE: Final[PageLimitInt] = TypeAdapter(
3234
PageLimitInt
3335
).validate_python(20)
@@ -70,7 +72,7 @@ class PageQueryParameters(RequestParameters):
7072

7173
class PageMetaInfoLimitOffset(BaseModel):
7274
limit: PositiveInt = DEFAULT_NUMBER_OF_ITEMS_PER_PAGE
73-
total: NonNegativeInt
75+
total: PageTotalCount
7476
offset: NonNegativeInt = 0
7577
count: NonNegativeInt
7678

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# pylint: disable=not-context-manager
2+
# pylint: disable=protected-access
3+
# pylint: disable=redefined-outer-name
4+
# pylint: disable=unused-argument
5+
# pylint: disable=unused-variable
6+
7+
8+
from models_library.api_schemas_catalog.services import LatestServiceGet, ServiceGetV2
9+
from models_library.api_schemas_webserver.catalog import (
10+
CatalogServiceUpdate,
11+
)
12+
from models_library.products import ProductName
13+
from models_library.rest_pagination import PageOffsetInt
14+
from models_library.rpc_pagination import PageLimitInt, PageRpc
15+
from models_library.services_history import ServiceRelease
16+
from models_library.services_types import ServiceKey, ServiceVersion
17+
from models_library.users import UserID
18+
from pydantic import NonNegativeInt, TypeAdapter
19+
from servicelib.rabbitmq._client_rpc import RabbitMQRPCClient
20+
21+
22+
class CatalogRpcSideEffects:
23+
# pylint: disable=no-self-use
24+
async def list_services_paginated(
25+
self,
26+
rpc_client: RabbitMQRPCClient,
27+
*,
28+
product_name: ProductName,
29+
user_id: UserID,
30+
limit: PageLimitInt,
31+
offset: NonNegativeInt,
32+
):
33+
assert rpc_client
34+
assert product_name
35+
assert user_id
36+
37+
items = TypeAdapter(list[LatestServiceGet]).validate_python(
38+
LatestServiceGet.model_json_schema()["examples"],
39+
)
40+
total_count = len(items)
41+
42+
return PageRpc[LatestServiceGet].create(
43+
items[offset : offset + limit],
44+
total=total_count,
45+
limit=limit,
46+
offset=offset,
47+
)
48+
49+
async def get_service(
50+
self,
51+
rpc_client: RabbitMQRPCClient,
52+
*,
53+
product_name: ProductName,
54+
user_id: UserID,
55+
service_key: ServiceKey,
56+
service_version: ServiceVersion,
57+
):
58+
assert rpc_client
59+
assert product_name
60+
assert user_id
61+
62+
got = ServiceGetV2.model_validate(
63+
ServiceGetV2.model_json_schema()["examples"][0]
64+
)
65+
got.version = service_version
66+
got.key = service_key
67+
68+
return got
69+
70+
async def update_service(
71+
self,
72+
rpc_client: RabbitMQRPCClient,
73+
*,
74+
product_name: ProductName,
75+
user_id: UserID,
76+
service_key: ServiceKey,
77+
service_version: ServiceVersion,
78+
update: CatalogServiceUpdate,
79+
):
80+
assert rpc_client
81+
assert product_name
82+
assert user_id
83+
84+
got = ServiceGetV2.model_validate(
85+
ServiceGetV2.model_json_schema()["examples"][0]
86+
)
87+
got.version = service_version
88+
got.key = service_key
89+
return got.model_copy(update=update.model_dump(exclude_unset=True))
90+
91+
async def list_my_service_history_paginated(
92+
self,
93+
rpc_client: RabbitMQRPCClient,
94+
*,
95+
product_name: ProductName,
96+
user_id: UserID,
97+
service_key: ServiceKey,
98+
offset: PageOffsetInt,
99+
limit: PageLimitInt,
100+
) -> PageRpc[ServiceRelease]:
101+
102+
assert rpc_client
103+
assert product_name
104+
assert user_id
105+
assert service_key
106+
107+
items = TypeAdapter(list[ServiceRelease]).validate_python(
108+
ServiceRelease.model_json_schema()["examples"],
109+
)
110+
total_count = len(items)
111+
112+
return PageRpc[ServiceRelease].create(
113+
items[offset : offset + limit],
114+
total=total_count,
115+
limit=limit,
116+
offset=offset,
117+
)

packages/service-library/src/servicelib/fastapi/http_error.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
from collections.abc import Awaitable, Callable
23
from typing import TypeVar
34

@@ -10,6 +11,9 @@
1011
from fastapi.responses import JSONResponse
1112
from pydantic import ValidationError
1213

14+
from ..logging_errors import create_troubleshotting_log_kwargs
15+
from ..status_codes_utils import is_5xx_server_error
16+
1317
validation_error_response_definition["properties"] = {
1418
"errors": {
1519
"title": "Validation errors",
@@ -21,6 +25,8 @@
2125

2226
TException = TypeVar("TException")
2327

28+
_logger = logging.getLogger(__name__)
29+
2430

2531
def make_http_error_handler_for_exception(
2632
status_code: int,
@@ -36,12 +42,24 @@ def make_http_error_handler_for_exception(
3642
SEE https://docs.python.org/3/library/exceptions.html#concrete-exceptions
3743
"""
3844

39-
async def _http_error_handler(_: Request, exc: Exception) -> JSONResponse:
45+
async def _http_error_handler(request: Request, exc: Exception) -> JSONResponse:
4046
assert isinstance(exc, exception_cls) # nosec
4147
error_content = {
4248
"errors": error_extractor(exc) if error_extractor else [f"{exc}"]
4349
}
4450

51+
if is_5xx_server_error(status_code):
52+
_logger.exception(
53+
create_troubleshotting_log_kwargs(
54+
"Unexpected error happened in the Resource Usage Tracker. Please contact support.",
55+
error=exc,
56+
error_context={
57+
"request": request,
58+
"request.method": f"{request.method}",
59+
},
60+
)
61+
)
62+
4563
return JSONResponse(
4664
content=jsonable_encoder(
4765
{"error": error_content} if envelope_error else error_content

packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/catalog/services.py

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,23 @@
77
from models_library.api_schemas_catalog.services import (
88
LatestServiceGet,
99
MyServiceGet,
10+
PageRpcLatestServiceGet,
11+
PageRpcServiceRelease,
1012
ServiceGetV2,
13+
ServiceRelease,
1114
ServiceUpdateV2,
1215
)
1316
from models_library.products import ProductName
1417
from models_library.rabbitmq_basic_types import RPCMethodName
18+
from models_library.rest_pagination import PageOffsetInt
1519
from models_library.rpc_pagination import (
1620
DEFAULT_NUMBER_OF_ITEMS_PER_PAGE,
1721
PageLimitInt,
1822
PageRpc,
1923
)
2024
from models_library.services_types import ServiceKey, ServiceVersion
2125
from models_library.users import UserID
22-
from pydantic import NonNegativeInt, TypeAdapter, validate_call
26+
from pydantic import TypeAdapter, validate_call
2327
from servicelib.logging_utils import log_decorator
2428
from servicelib.rabbitmq._constants import RPC_REQUEST_DEFAULT_TIMEOUT_S
2529

@@ -34,8 +38,8 @@ async def list_services_paginated( # pylint: disable=too-many-arguments
3438
product_name: ProductName,
3539
user_id: UserID,
3640
limit: PageLimitInt = DEFAULT_NUMBER_OF_ITEMS_PER_PAGE,
37-
offset: NonNegativeInt = 0,
38-
) -> PageRpc[LatestServiceGet]:
41+
offset: PageOffsetInt = 0,
42+
) -> PageRpcLatestServiceGet:
3943
"""
4044
Raises:
4145
ValidationError: on invalid arguments
@@ -47,7 +51,7 @@ async def _call(
4751
product_name: ProductName,
4852
user_id: UserID,
4953
limit: PageLimitInt,
50-
offset: NonNegativeInt,
54+
offset: PageOffsetInt,
5155
):
5256
return await rpc_client.request(
5357
CATALOG_RPC_NAMESPACE,
@@ -235,3 +239,51 @@ async def _call(
235239
result = await _call(product_name=product_name, user_id=user_id, ids=ids)
236240
assert TypeAdapter(list[MyServiceGet]).validate_python(result) is not None # nosec
237241
return cast(list[MyServiceGet], result)
242+
243+
244+
async def list_my_service_history_paginated( # pylint: disable=too-many-arguments
245+
rpc_client: RabbitMQRPCClient,
246+
*,
247+
product_name: ProductName,
248+
user_id: UserID,
249+
service_key: ServiceKey,
250+
limit: PageLimitInt = DEFAULT_NUMBER_OF_ITEMS_PER_PAGE,
251+
offset: PageOffsetInt = 0,
252+
) -> PageRpcServiceRelease:
253+
"""
254+
Raises:
255+
ValidationError: on invalid arguments
256+
"""
257+
258+
@validate_call()
259+
async def _call(
260+
product_name: ProductName,
261+
user_id: UserID,
262+
service_key: ServiceKey,
263+
limit: PageLimitInt,
264+
offset: PageOffsetInt,
265+
):
266+
return await rpc_client.request(
267+
CATALOG_RPC_NAMESPACE,
268+
TypeAdapter(RPCMethodName).validate_python(
269+
"list_my_service_history_paginated"
270+
),
271+
product_name=product_name,
272+
user_id=user_id,
273+
service_key=service_key,
274+
limit=limit,
275+
offset=offset,
276+
)
277+
278+
result = await _call(
279+
product_name=product_name,
280+
user_id=user_id,
281+
service_key=service_key,
282+
limit=limit,
283+
offset=offset,
284+
)
285+
286+
assert ( # nosec
287+
TypeAdapter(PageRpcServiceRelease).validate_python(result) is not None
288+
)
289+
return cast(PageRpc[ServiceRelease], result)

packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/paths.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
AsyncJobNameData,
66
)
77
from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE
8+
from models_library.products import ProductName
89
from models_library.projects_nodes_io import LocationID
910
from models_library.rabbitmq_basic_types import RPCMethodName
1011
from models_library.users import UserID
@@ -17,7 +18,7 @@ async def compute_path_size(
1718
client: RabbitMQRPCClient,
1819
*,
1920
user_id: UserID,
20-
product_name: str,
21+
product_name: ProductName,
2122
location_id: LocationID,
2223
path: Path,
2324
) -> tuple[AsyncJobGet, AsyncJobNameData]:

0 commit comments

Comments
 (0)