Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def handle_aiohttp_web_http_error(
exception.content_type = MIMETYPE_APPLICATION_JSON
if exception.reason:
exception.set_status(
exception.status, safe_status_message(message=exception.reason)
exception.status, reason=safe_status_message(message=exception.reason)
)

if not exception.text or not is_enveloped_from_text(exception.text):
Expand Down Expand Up @@ -165,7 +165,7 @@ def _handle_aiohttp_web_http_successful(
exception.content_type = MIMETYPE_APPLICATION_JSON
if exception.reason:
exception.set_status(
exception.status, safe_status_message(message=exception.reason)
exception.status, reason=safe_status_message(message=exception.reason)
)

if exception.text and not is_enveloped_from_text(exception.text):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ async def get_api_key(
if api_key is not None:
return api_key

raise ApiKeyNotFoundError(api_key_id=api_key_id)
raise ApiKeyNotFoundError(
api_key_id=api_key_id, product_name=product_name, user_id=user_id
)


async def list_api_keys(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from ..errors import WebServerBaseError


class ApiKeysValueError(WebServerBaseError, ValueError):
...
class ApiKeysValueError(WebServerBaseError, ValueError): ...


class ApiKeyDuplicatedDisplayNameError(ApiKeysValueError):
msg_template = "API Key with display name '{display_name}' already exists. {reason}"
msg_template = (
"API Key with display name '{display_name}' already exists: {details}"
)


class ApiKeyNotFoundError(ApiKeysValueError):
msg_template = "API Key with ID '{api_key_id}' not found. {reason}"
msg_template = "API Key with ID '{api_key_id}' not found: {details}"
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import aiohttp
from aiohttp import ClientSession, ClientTimeout, web
from servicelib.aiohttp import status
from servicelib.mimetype_constants import MIMETYPE_APPLICATION_JSON
from tenacity import retry
from tenacity.before_sleep import before_sleep_log
from tenacity.stop import stop_after_attempt
Expand Down Expand Up @@ -36,13 +37,14 @@


def _get_exception_from(
status_code: int, on_error: _StatusToExceptionMapping | None, reason: str, url: URL
status_code: int, on_error: _StatusToExceptionMapping | None, details: str, url: URL
):
if on_error and status_code in on_error:
exc, exc_ctx = on_error[status_code]
return exc(**exc_ctx, status=status_code, reason=reason)
exc_cls, exc_ctx = on_error[status_code]
return exc_cls(**exc_ctx, status=status_code, details=details)

# default
return DirectorV2ServiceError(status=status_code, reason=reason, url=url)
return DirectorV2ServiceError(status=status_code, details=details, url=url)


@retry(**DEFAULT_RETRY_POLICY)
Expand All @@ -61,13 +63,13 @@ async def _make_request(
) as response:
payload: dict[str, Any] | list[dict[str, Any]] | None | str = (
await response.json()
if response.content_type == "application/json"
if response.content_type == MIMETYPE_APPLICATION_JSON
else await response.text()
)

if response.status != expected_status.status_code:
raise _get_exception_from(
response.status, on_error, reason=f"{payload}", url=url
response.status, on_error, details=f"{payload}", url=url
)
return payload

Expand Down Expand Up @@ -99,13 +101,13 @@ async def request_director_v2(
except TimeoutError as err:
raise DirectorV2ServiceError(
status=status.HTTP_503_SERVICE_UNAVAILABLE,
reason=f"request to director-v2 timed-out: {err}",
details=f"request to director-v2 timed-out: {err}",
url=url,
) from err

except aiohttp.ClientError as err:
raise DirectorV2ServiceError(
status=status.HTTP_503_SERVICE_UNAVAILABLE,
reason=f"request to director-v2 service unexpected error {err}",
details=f"request to director-v2 service unexpected error {err}",
url=url,
) from err
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ async def _handler_director_service_error_as_503_or_4xx(
WalletNotEnoughCreditsError: HttpErrorInfo(
status.HTTP_402_PAYMENT_REQUIRED,
user_message(
"Your wallet does not have sufficient credits to run this computation. {reason}",
"Your wallet does not have sufficient credits to run this computation: {details}",
_version=1,
),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ async def start_computation(request: web.Request) -> web.Response:
now = datetime.now(UTC)
if now - created_at > timedelta(minutes=5):
raise web.HTTPBadRequest(
reason=(
text=(
"This client generated collection is not new, "
"it was created more than 5 minutes ago. "
"Therefore, the client is probably wrongly generating it."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
class DirectorV2ServiceError(WebServerBaseError, RuntimeError):
"""Basic exception for errors raised by director-v2"""

msg_template = "Unexpected error: director-v2 returned '{status}', reason '{reason}' after calling '{url}'"
msg_template = "Unexpected error: director-v2 returned '{status}', details '{details}' after calling '{url}'"

def __init__(self, *, status: int, reason: str, **ctx: Any) -> None:
def __init__(self, *, status: int, details: str, **ctx: Any) -> None:
super().__init__(**ctx)
self.status = status
self.reason = reason
self.details = details


class ComputationNotFoundError(DirectorV2ServiceError):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def create_http_error_exception_handlers_map() -> ExceptionHandlersMap:
"""
exc_handlers_map: ExceptionHandlersMap = {
exc_type: create_exception_handler_from_http_info(
status_code=code, msg_template="{reason}"
status_code=code, msg_template="{text}"
)
for code, exc_type in _STATUS_CODE_TO_HTTP_ERRORS.items()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@
),
FolderValueNotPermittedError: HttpErrorInfo(
status.HTTP_409_CONFLICT,
user_message("The folder operation cannot be completed: {reason}", _version=1),
user_message("The folder operation cannot be completed: {details}", _version=1),
),
FoldersValueError: HttpErrorInfo(
status.HTTP_409_CONFLICT,
user_message("The folder configuration is invalid: {reason}", _version=1),
user_message("The folder configuration is invalid: {details}", _version=1),
),
ProjectInvalidRightsError: HttpErrorInfo(
status.HTTP_403_FORBIDDEN,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ async def get(
row = result.first()
if row is None:
raise FolderAccessForbiddenError(
reason=f"Folder {folder_id} does not exist.",
details=f"Folder {folder_id} does not exist.",
)
return FolderDB.model_validate(row)

Expand Down Expand Up @@ -369,7 +369,7 @@ async def get_for_user_or_workspace(
row = await result.first()
if row is None:
raise FolderAccessForbiddenError(
reason=f"User does not have access to the folder {folder_id}. Or folder does not exist.",
details=f"User does not have access to the folder {folder_id}. Or folder does not exist.",
)
return FolderDB.model_validate(row)

Expand Down Expand Up @@ -421,7 +421,7 @@ async def update(
result = await conn.stream(query)
row = await result.first()
if row is None:
raise FolderNotFoundError(reason=f"Folder {folders_id_or_ids} not found.")
raise FolderNotFoundError(details=f"Folder {folders_id_or_ids} not found.")
return FolderDB.model_validate(row)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ async def create_folder(
if workspace_id and parent_folder_db.workspace_id != workspace_id:
# Check parent folder id exists inside the same workspace
raise WorkspaceAccessForbiddenError(
reason=f"Folder {parent_folder_id} does not exists in workspace {workspace_id}."
details=f"Folder {parent_folder_id} does not exists in workspace {workspace_id}."
)

folder_db = await _folders_repository.create(
Expand Down Expand Up @@ -291,7 +291,7 @@ async def update_folder(
)
if parent_folder_id in _child_folders:
raise FolderValueNotPermittedError(
reason="Parent folder id should not be one of children"
details="Parent folder id should not be one of children"
)

folder_db = await _folders_repository.update(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ async def delete_trashed_folder(
raise FolderNotTrashedError(
folder_id=folder_id,
user_id=user_id,
reason="Cannot delete trashed folder since it does not fit current criteria",
details="Cannot delete trashed folder since it does not fit current criteria",
)

# NOTE: this function deletes folder AND its content recursively!
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@
from ..errors import WebServerBaseError


class FoldersValueError(WebServerBaseError, ValueError):
...
class FoldersValueError(WebServerBaseError, ValueError): ...


class FolderValueNotPermittedError(FoldersValueError):
msg_template = "Provided value is not permitted. {reason}"
msg_template = "Provided value is not permitted: {details}"


class FolderNotFoundError(FoldersValueError):
msg_template = "Folder not found. {reason}"
msg_template = "Folder not found: {details}"


class FolderAccessForbiddenError(FoldersValueError):
msg_template = "Folder access forbidden. {reason}"
msg_template = "Folder access forbidden: {details}"


class FolderGroupNotFoundError(FoldersValueError):
msg_template = "Folder group not found. {reason}"
msg_template = "Folder group not found: {details}"


class FoldersRuntimeError(WebServerBaseError, RuntimeError):
...
class FoldersRuntimeError(WebServerBaseError, RuntimeError): ...


class FolderNotTrashedError(FoldersRuntimeError):
msg_template = (
"Cannot delete folder {folder_id} since it was not trashed first: {reason}"
"Cannot delete folder {folder_id} since it was not trashed first: {details}"
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ async def build_rrids_tree_view(
) -> dict[str, Any]:
if tree_view_mode != "std":
raise web.HTTPNotImplemented(
reason="Currently only 'std' option for the classifiers tree view is implemented"
text="Currently only 'std' option for the classifiers tree view is implemented"
)

scicrunch = SciCrunch.get_instance(app)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ async def validate_invitation_url(
"""
if current_product.group_id is None:
raise InvitationsServiceUnavailableError(
reason="Current product is not configured for invitations",
details="Current product is not configured for invitations",
current_product=current_product,
guest_email=guest_email,
)
Expand All @@ -60,7 +60,7 @@ async def validate_invitation_url(
valid_url = TypeAdapter(AnyHttpUrl).validate_python(invitation_url)
except ValidationError as err:
raise InvalidInvitationError(
reason=MSG_INVALID_INVITATION_URL,
details=MSG_INVALID_INVITATION_URL,
current_product=current_product,
guest_email=guest_email,
) from err
Expand All @@ -73,7 +73,7 @@ async def validate_invitation_url(
# check email
if invitation.guest.lower() != guest_email.lower():
raise InvalidInvitationError(
reason="This invitation was issued for a different email",
details="This invitation was issued for a different email",
current_product=current_product,
guest_email=guest_email,
invitation=invitation,
Expand All @@ -83,7 +83,7 @@ async def validate_invitation_url(
assert current_product.group_id is not None # nosec
if invitation.product is not None and invitation.product != current_product.name:
raise InvalidInvitationError(
reason="This invitation was issued for a different product. "
details="This invitation was issued for a different product. "
f"Got '{invitation.product}', expected '{current_product.name}'",
guest_email=guest_email,
current_product=current_product,
Expand All @@ -101,7 +101,7 @@ async def validate_invitation_url(
if is_user_registered_in_product:
# NOTE: a user might be already registered but the invitation is for another product
raise InvalidInvitationError(
reason=MSG_INVITATION_ALREADY_USED,
details=MSG_INVITATION_ALREADY_USED,
guest_email=guest_email,
current_product=current_product,
invitation=invitation,
Expand All @@ -124,7 +124,7 @@ async def extract_invitation(
try:
valid_url = TypeAdapter(AnyHttpUrl).validate_python(invitation_url)
except ValidationError as err:
raise InvalidInvitationError(reason=MSG_INVALID_INVITATION_URL) from err
raise InvalidInvitationError(details=MSG_INVALID_INVITATION_URL) from err

# check with service
invitation: ApiInvitationContent = await get_invitations_service_api(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ class InvalidInvitationError(InvitationsError):


class InvitationsServiceUnavailableError(InvitationsError):
msg_template = "Cannot process invitations"
msg_template = "Cannot process invitations: {details}"
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ async def purchase_licensed_item(
# Check whether wallet has enough credits
if wallet.available_credits - pricing_unit.current_cost_per_unit < 0:
raise WalletNotEnoughCreditsError(
reason=f"Wallet '{wallet.name}' has {wallet.available_credits} credits."
details=f"Wallet '{wallet.name}' has {wallet.available_credits} credits."
)

user = await users_service.get_user(app, user_id=user_id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

from aiohttp import web
from aiohttp.web import RouteTableDef
from common_library.user_messages import user_message
from models_library.authentification import TwoFactorAuthentificationMethod
from pydantic import TypeAdapter
from servicelib.aiohttp import status
from servicelib.aiohttp.requests_validation import parse_request_body_as
from servicelib.logging_utils import get_log_record_extra, log_context
from servicelib.mimetype_constants import MIMETYPE_APPLICATION_JSON
from servicelib.request_keys import RQT_USERID_KEY
from simcore_postgres_database.models.users import UserRole

Expand Down Expand Up @@ -212,8 +212,7 @@ async def login_2fa(request: web.Request):
)
if not settings.LOGIN_2FA_REQUIRED:
raise web.HTTPServiceUnavailable(
reason="2FA login is not available",
content_type=MIMETYPE_APPLICATION_JSON,
text=user_message("2FA login is not available"),
)

# validates input params
Expand All @@ -224,13 +223,9 @@ async def login_2fa(request: web.Request):
request.app, login_2fa_.email
)
if not _expected_2fa_code:
raise web.HTTPUnauthorized(
reason=MSG_WRONG_2FA_CODE__EXPIRED, content_type=MIMETYPE_APPLICATION_JSON
)
raise web.HTTPUnauthorized(text=MSG_WRONG_2FA_CODE__EXPIRED)
if login_2fa_.code.get_secret_value() != _expected_2fa_code:
raise web.HTTPUnauthorized(
reason=MSG_WRONG_2FA_CODE__INVALID, content_type=MIMETYPE_APPLICATION_JSON
)
raise web.HTTPUnauthorized(text=MSG_WRONG_2FA_CODE__INVALID)

user = _auth_service.check_not_null_user(
await _auth_service.get_user_or_none(request.app, email=login_2fa_.email)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def _sender():

except TwilioException as exc:
raise SendingVerificationSmsError(
reason=f"Could not send SMS to {mask_phone_number(phone_number)}",
details=f"Could not send SMS to {mask_phone_number(phone_number)}",
user_id=user_id,
twilio_error=exc,
) from exc
Expand Down Expand Up @@ -176,7 +176,7 @@ async def send_email_code(
)
except Exception as exc:
raise SendingVerificationEmailError(
reason=f"Could not send email to {user_email}",
details=f"Could not send email to {user_email}",
user_id=user_id,
user_email=user_email,
email_error=exc,
Expand Down
Loading
Loading