Skip to content

Commit 39f310a

Browse files
committed
raises unavailble
1 parent 2c42843 commit 39f310a

File tree

5 files changed

+53
-34
lines changed

5 files changed

+53
-34
lines changed

services/payments/src/simcore_service_payments/_constants.py

+3
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44
PAG: Final[str] = "Payments Gateway service"
55
PGDB: Final[str] = "Postgres service"
66
RUT: Final[str] = "Resource Usage Tracker service"
7+
8+
9+
MSG_GATEWAY_UNAVAILABLE_ERROR = "Our payments provider is temporary unavailable"

services/payments/src/simcore_service_payments/core/errors.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ def get_full_class_name(cls) -> str:
1313
#
1414

1515

16-
class PaymentsGatewayError(_BaseAppError):
16+
class BasePaymentsGatewayError(_BaseAppError):
1717
...
1818

1919

20-
class PaymentsGatewayNotReadyError(PaymentsGatewayError):
20+
class PaymentsGatewayNotReadyError(BasePaymentsGatewayError):
2121
msg_template = "Payments-Gateway is unresponsive: {checks}"

services/payments/src/simcore_service_payments/services/payments_gateway.py

+34-18
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
from fastapi import FastAPI
1717
from fastapi.encoders import jsonable_encoder
1818
from httpx import URL, HTTPStatusError
19+
from models_library.api_schemas_payments.errors import PaymentServiceUnavailableError
1920
from models_library.api_schemas_webserver.wallets import PaymentID, PaymentMethodID
2021
from pydantic import ValidationError, parse_raw_as
21-
from pydantic.errors import PydanticErrorMixin
2222
from servicelib.fastapi.app_state import SingletonInAppStateMixin
2323
from servicelib.fastapi.http_client import BaseHttpApi
2424
from servicelib.fastapi.httpx_utils import to_curl_command
@@ -29,7 +29,8 @@
2929
from tenacity.retry import retry_if_exception_type
3030
from tenacity.wait import wait_exponential
3131

32-
from ..core.errors import PaymentsGatewayNotReadyError
32+
from .._constants import MSG_GATEWAY_UNAVAILABLE_ERROR, PAG
33+
from ..core.errors import BasePaymentsGatewayError, PaymentsGatewayNotReadyError
3334
from ..core.settings import ApplicationSettings
3435
from ..models.payments_gateway import (
3536
BatchGetPaymentMethods,
@@ -53,13 +54,13 @@ def _parse_raw_as_or_none(cls: type, text: str | None):
5354
return None
5455

5556

56-
class PaymentsGatewayError(PydanticErrorMixin, ValueError):
57+
class PaymentsGatewayApiError(BasePaymentsGatewayError):
5758
msg_template = "{operation_id} error {status_code}: {reason}"
5859

5960
@classmethod
6061
def from_http_status_error(
6162
cls, err: HTTPStatusError, operation_id: str
62-
) -> "PaymentsGatewayError":
63+
) -> "PaymentsGatewayApiError":
6364
return cls(
6465
operation_id=f"PaymentsGatewayApi.{operation_id}",
6566
reason=f"{err}",
@@ -82,22 +83,37 @@ def get_detailed_message(self) -> str:
8283

8384

8485
@contextlib.contextmanager
85-
def _raise_as_payments_gateway_error(operation_id: str):
86+
def _reraise_as_service_errors_context(operation_id: str):
8687
try:
8788
yield
8889

89-
except HTTPStatusError as err:
90-
error = PaymentsGatewayError.from_http_status_error(
90+
except httpx.RequestError as err:
91+
_logger.exception("%s: request error", PAG)
92+
raise PaymentServiceUnavailableError(
93+
human_reason=MSG_GATEWAY_UNAVAILABLE_ERROR
94+
) from err
95+
96+
except httpx.HTTPStatusError as err:
97+
error = PaymentsGatewayApiError.from_http_status_error(
9198
err, operation_id=operation_id
9299
)
93-
_logger.warning(error.get_detailed_message())
94-
raise error from err
100+
101+
if err.response.is_client_error:
102+
_logger.warning(error.get_detailed_message())
103+
raise error from err
104+
105+
if err.response.is_server_error:
106+
# 5XX in server -> turn into unavailable
107+
_logger.exception(error.get_detailed_message())
108+
raise PaymentServiceUnavailableError(
109+
human_reason=MSG_GATEWAY_UNAVAILABLE_ERROR
110+
) from err
95111

96112

97-
def _handle_status_errors(coro: Callable):
113+
def _handle_httpx_errors(coro: Callable):
98114
@functools.wraps(coro)
99115
async def _wrapper(self, *args, **kwargs):
100-
with _raise_as_payments_gateway_error(operation_id=coro.__name__):
116+
with _reraise_as_service_errors_context(operation_id=coro.__name__):
101117
return await coro(self, *args, **kwargs)
102118

103119
return _wrapper
@@ -119,7 +135,7 @@ class PaymentsGatewayApi(BaseHttpApi, SingletonInAppStateMixin):
119135
# api: one-time-payment workflow
120136
#
121137

122-
@_handle_status_errors
138+
@_handle_httpx_errors
123139
async def init_payment(self, payment: InitPayment) -> PaymentInitiated:
124140
response = await self.client.post(
125141
"/init",
@@ -131,7 +147,7 @@ async def init_payment(self, payment: InitPayment) -> PaymentInitiated:
131147
def get_form_payment_url(self, id_: PaymentID) -> URL:
132148
return self.client.base_url.copy_with(path="/pay", params={"id": f"{id_}"})
133149

134-
@_handle_status_errors
150+
@_handle_httpx_errors
135151
async def cancel_payment(
136152
self, payment_initiated: PaymentInitiated
137153
) -> PaymentCancelled:
@@ -146,7 +162,7 @@ async def cancel_payment(
146162
# api: payment method workflows
147163
#
148164

149-
@_handle_status_errors
165+
@_handle_httpx_errors
150166
async def init_payment_method(
151167
self,
152168
payment_method: InitPaymentMethod,
@@ -165,7 +181,7 @@ def get_form_payment_method_url(self, id_: PaymentMethodID) -> URL:
165181

166182
# CRUD
167183

168-
@_handle_status_errors
184+
@_handle_httpx_errors
169185
async def get_many_payment_methods(
170186
self, ids_: list[PaymentMethodID]
171187
) -> list[GetPaymentMethod]:
@@ -178,18 +194,18 @@ async def get_many_payment_methods(
178194
response.raise_for_status()
179195
return PaymentMethodsBatch.parse_obj(response.json()).items
180196

181-
@_handle_status_errors
197+
@_handle_httpx_errors
182198
async def get_payment_method(self, id_: PaymentMethodID) -> GetPaymentMethod:
183199
response = await self.client.get(f"/payment-methods/{id_}")
184200
response.raise_for_status()
185201
return GetPaymentMethod.parse_obj(response.json())
186202

187-
@_handle_status_errors
203+
@_handle_httpx_errors
188204
async def delete_payment_method(self, id_: PaymentMethodID) -> None:
189205
response = await self.client.delete(f"/payment-methods/{id_}")
190206
response.raise_for_status()
191207

192-
@_handle_status_errors
208+
@_handle_httpx_errors
193209
async def pay_with_payment_method(
194210
self, id_: PaymentMethodID, payment: InitPayment
195211
) -> AckPaymentWithPaymentMethod:

services/payments/tests/unit/test_rpc_payments.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@
99
import pytest
1010
from faker import Faker
1111
from fastapi import FastAPI
12-
from models_library.api_schemas_payments.errors import PaymentNotFoundError
12+
from models_library.api_schemas_payments.errors import (
13+
PaymentNotFoundError,
14+
PaymentServiceUnavailableError,
15+
)
1316
from models_library.api_schemas_webserver.wallets import WalletPaymentInitiated
1417
from models_library.rabbitmq_basic_types import RPCMethodName
1518
from pydantic import parse_obj_as
1619
from pytest_mock import MockerFixture
1720
from pytest_simcore.helpers.typing_env import EnvVarsDict
1821
from pytest_simcore.helpers.utils_envs import setenvs_from_dict
1922
from respx import MockRouter
20-
from servicelib.rabbitmq import RabbitMQRPCClient, RPCServerError
23+
from servicelib.rabbitmq import RabbitMQRPCClient
2124
from servicelib.rabbitmq._constants import RPC_REQUEST_DEFAULT_TIMEOUT_S
2225
from simcore_service_payments.api.rpc.routes import PAYMENTS_RPC_NAMESPACE
2326

@@ -87,19 +90,16 @@ async def test_rpc_init_payment_fail(
8790
):
8891
assert app
8992

90-
with pytest.raises(RPCServerError) as exc_info:
93+
with pytest.raises(PaymentServiceUnavailableError) as exc_info:
9194
await rpc_client.request(
9295
PAYMENTS_RPC_NAMESPACE,
9396
parse_obj_as(RPCMethodName, "init_payment"),
9497
**init_payment_kwargs,
9598
timeout_s=None if is_pdb_enabled else 5,
9699
)
97100

98-
error = exc_info.value
99-
assert isinstance(error, RPCServerError)
100-
assert error.exc_type == "httpx.ConnectError"
101-
assert error.method_name == "init_payment"
102-
assert error.msg
101+
# FIXME: should raise
102+
assert isinstance(exc_info.value, PaymentServiceUnavailableError)
103103

104104

105105
async def test_webserver_one_time_payment_workflow(

services/payments/tests/unit/test_services_payments_gateway.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
)
1818
from simcore_service_payments.services.payments_gateway import (
1919
PaymentsGatewayApi,
20-
PaymentsGatewayError,
21-
_raise_as_payments_gateway_error,
20+
PaymentsGatewayApiError,
21+
_reraise_as_service_errors_context,
2222
setup_payments_gateway,
2323
)
2424

@@ -187,7 +187,7 @@ async def test_payment_methods_workflow(
187187
# delete payment-method
188188
await payments_gateway_api.delete_payment_method(payment_method_id)
189189

190-
with pytest.raises(PaymentsGatewayError) as err_info:
190+
with pytest.raises(PaymentsGatewayApiError) as err_info:
191191
await payments_gateway_api.get_payment_method(payment_method_id)
192192

193193
assert str(err_info.value)
@@ -205,18 +205,18 @@ async def test_payment_methods_workflow(
205205

206206
async def test_payments_gateway_error_exception():
207207
async def _go():
208-
with _raise_as_payments_gateway_error(operation_id="foo"):
208+
with _reraise_as_service_errors_context(operation_id="foo"):
209209
async with httpx.AsyncClient(
210210
app=FastAPI(),
211211
base_url="http://payments.testserver.io",
212212
) as client:
213213
response = await client.post("/foo", params={"x": "3"}, json={"y": 12})
214214
response.raise_for_status()
215215

216-
with pytest.raises(PaymentsGatewayError) as err_info:
216+
with pytest.raises(PaymentsGatewayApiError) as err_info:
217217
await _go()
218218
err = err_info.value
219-
assert isinstance(err, PaymentsGatewayError)
219+
assert isinstance(err, PaymentsGatewayApiError)
220220

221221
assert "curl -X POST" in err.get_detailed_message()
222222

0 commit comments

Comments
 (0)