Skip to content

Commit e37b695

Browse files
committed
raises unavailble
1 parent 1e14653 commit e37b695

File tree

5 files changed

+53
-29
lines changed

5 files changed

+53
-29
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 (
2424
AttachLifespanMixin,
@@ -33,7 +33,8 @@
3333
from tenacity.retry import retry_if_exception_type
3434
from tenacity.wait import wait_exponential
3535

36-
from ..core.errors import PaymentsGatewayNotReadyError
36+
from .._constants import MSG_GATEWAY_UNAVAILABLE_ERROR, PAG
37+
from ..core.errors import BasePaymentsGatewayError, PaymentsGatewayNotReadyError
3738
from ..core.settings import ApplicationSettings
3839
from ..models.payments_gateway import (
3940
BatchGetPaymentMethods,
@@ -57,13 +58,13 @@ def _parse_raw_as_or_none(cls: type, text: str | None):
5758
return None
5859

5960

60-
class PaymentsGatewayError(PydanticErrorMixin, ValueError):
61+
class PaymentsGatewayApiError(BasePaymentsGatewayError):
6162
msg_template = "{operation_id} error {status_code}: {reason}"
6263

6364
@classmethod
6465
def from_http_status_error(
6566
cls, err: HTTPStatusError, operation_id: str
66-
) -> "PaymentsGatewayError":
67+
) -> "PaymentsGatewayApiError":
6768
return cls(
6869
operation_id=f"PaymentsGatewayApi.{operation_id}",
6970
reason=f"{err}",
@@ -86,22 +87,37 @@ def get_detailed_message(self) -> str:
8687

8788

8889
@contextlib.contextmanager
89-
def _raise_as_payments_gateway_error(operation_id: str):
90+
def _reraise_as_service_errors_context(operation_id: str):
9091
try:
9192
yield
9293

93-
except HTTPStatusError as err:
94-
error = PaymentsGatewayError.from_http_status_error(
94+
except httpx.RequestError as err:
95+
_logger.exception("%s: request error", PAG)
96+
raise PaymentServiceUnavailableError(
97+
human_reason=MSG_GATEWAY_UNAVAILABLE_ERROR
98+
) from err
99+
100+
except httpx.HTTPStatusError as err:
101+
error = PaymentsGatewayApiError.from_http_status_error(
95102
err, operation_id=operation_id
96103
)
97-
_logger.warning(error.get_detailed_message())
98-
raise error from err
104+
105+
if err.response.is_client_error:
106+
_logger.warning(error.get_detailed_message())
107+
raise error from err
108+
109+
if err.response.is_server_error:
110+
# 5XX in server -> turn into unavailable
111+
_logger.exception(error.get_detailed_message())
112+
raise PaymentServiceUnavailableError(
113+
human_reason=MSG_GATEWAY_UNAVAILABLE_ERROR
114+
) from err
99115

100116

101-
def _handle_status_errors(coro: Callable):
117+
def _handle_httpx_errors(coro: Callable):
102118
@functools.wraps(coro)
103119
async def _wrapper(self, *args, **kwargs):
104-
with _raise_as_payments_gateway_error(operation_id=coro.__name__):
120+
with _reraise_as_service_errors_context(operation_id=coro.__name__):
105121
return await coro(self, *args, **kwargs)
106122

107123
return _wrapper
@@ -125,7 +141,7 @@ class PaymentsGatewayApi(
125141
# api: one-time-payment workflow
126142
#
127143

128-
@_handle_status_errors
144+
@_handle_httpx_errors
129145
async def init_payment(self, payment: InitPayment) -> PaymentInitiated:
130146
response = await self.client.post(
131147
"/init",
@@ -137,7 +153,7 @@ async def init_payment(self, payment: InitPayment) -> PaymentInitiated:
137153
def get_form_payment_url(self, id_: PaymentID) -> URL:
138154
return self.client.base_url.copy_with(path="/pay", params={"id": f"{id_}"})
139155

140-
@_handle_status_errors
156+
@_handle_httpx_errors
141157
async def cancel_payment(
142158
self, payment_initiated: PaymentInitiated
143159
) -> PaymentCancelled:
@@ -152,7 +168,7 @@ async def cancel_payment(
152168
# api: payment method workflows
153169
#
154170

155-
@_handle_status_errors
171+
@_handle_httpx_errors
156172
async def init_payment_method(
157173
self,
158174
payment_method: InitPaymentMethod,
@@ -171,7 +187,7 @@ def get_form_payment_method_url(self, id_: PaymentMethodID) -> URL:
171187

172188
# CRUD
173189

174-
@_handle_status_errors
190+
@_handle_httpx_errors
175191
async def get_many_payment_methods(
176192
self, ids_: list[PaymentMethodID]
177193
) -> list[GetPaymentMethod]:
@@ -184,18 +200,18 @@ async def get_many_payment_methods(
184200
response.raise_for_status()
185201
return PaymentMethodsBatch.parse_obj(response.json()).items
186202

187-
@_handle_status_errors
203+
@_handle_httpx_errors
188204
async def get_payment_method(self, id_: PaymentMethodID) -> GetPaymentMethod:
189205
response = await self.client.get(f"/payment-methods/{id_}")
190206
response.raise_for_status()
191207
return GetPaymentMethod.parse_obj(response.json())
192208

193-
@_handle_status_errors
209+
@_handle_httpx_errors
194210
async def delete_payment_method(self, id_: PaymentMethodID) -> None:
195211
response = await self.client.delete(f"/payment-methods/{id_}")
196212
response.raise_for_status()
197213

198-
@_handle_status_errors
214+
@_handle_httpx_errors
199215
async def pay_with_payment_method(
200216
self, id_: PaymentMethodID, payment: InitPayment
201217
) -> AckPaymentWithPaymentMethod:

services/payments/tests/unit/test_rpc_payments.py

+8-3
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,7 +90,7 @@ 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"),
@@ -101,6 +104,8 @@ async def test_rpc_init_payment_fail(
101104
assert error.method_name == "init_payment"
102105
assert error.exc_message
103106
assert error.traceback
107+
# FIXME: should raise
108+
assert isinstance(exc_info.value, PaymentServiceUnavailableError)
104109

105110

106111
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)