From 8239f777ec34a19dcd02ef00e02dd54605de1b23 Mon Sep 17 00:00:00 2001 From: Pedro Crespo-Valero <32402063+pcrespov@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:15:16 +0100 Subject: [PATCH] generalized --- .../api_schemas_webserver/groups.py | 24 ++++++++------ .../models_library/utils/common_validators.py | 32 +++++++++++++++++++ 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_webserver/groups.py b/packages/models-library/src/models_library/api_schemas_webserver/groups.py index f83bc5386086..13bfe711dfb5 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/groups.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/groups.py @@ -10,9 +10,10 @@ root_validator, validator, ) -from simcore_service_webserver.db.base_repository import UserID from ..emails import LowerCaseEmailStr +from ..users import UserID +from ..utils.common_validators import create__only_one_is_set__root_validator from ._base import InputSchema, OutputSchema @@ -182,15 +183,18 @@ class GroupUserAdd(InputSchema): uid: UserID | None = None email: LowerCaseEmailStr | None = None - @root_validator - @classmethod - def _check_uid_or_email(cls, values): - uid, email = values.get("uid"), values.get("email") - # Ensure exactly one of uid or email is set - if not (bool(uid is not None) ^ bool(email is not None)): - msg = f"Either 'uid' or 'email' must be set, but not both. Got {uid=} and {email=}" - raise ValueError(msg) - return values + _check_uid_or_email = root_validator(allow_reuse=True)( + create__only_one_is_set__root_validator(["uid", "email"]) + ) + # @root_validator + # @classmethod + # def _check_uid_or_email(cls, values): + # uid, email = values.get("uid"), values.get("email") + # # Ensure exactly one of uid or email is set + # if not (bool(uid is not None) ^ bool(email is not None)): + # msg = f"Either 'uid' or 'email' must be set, but not both. Got {uid=} and {email=}" + # raise ValueError(msg) + # return values class GroupUserUpdate(InputSchema): diff --git a/packages/models-library/src/models_library/utils/common_validators.py b/packages/models-library/src/models_library/utils/common_validators.py index c60586dfa92c..baf09445ffe8 100644 --- a/packages/models-library/src/models_library/utils/common_validators.py +++ b/packages/models-library/src/models_library/utils/common_validators.py @@ -16,6 +16,8 @@ class MyModel(BaseModel): """ import enum +import functools +import operator from typing import Any @@ -69,3 +71,33 @@ def null_or_none_str_to_none_validator(value: Any): if isinstance(value, str) and value.lower() in ("null", "none"): return None return value + + +def create__only_one_is_set__root_validator(alternative_field_names: list[str]): + """Ensure exactly one and only one of the alternatives is set + + This is useful when you want to give the client alternative + ways to set the same thing e.g. set the user by email or id or username + and each of those has a different field + + NOTE: Alternatevely, the previous example can also be solved using a + single field as `user: Email | UserID | UserName` + + SEE test_uid_or_email_are_set.py for more details + """ + + def _validator(cls, values): + assert set(alternative_field_names).issubset(cls.__fields__) # nosec + + got = { + field_name: values.get(field_name) for field_name in alternative_field_names + } + + if not functools.reduce(operator.xor, (v is not None for v in got.values())): + msg = ( + f"Either { 'or'.join(got.keys()) } must be set, but not both. Got {got}" + ) + raise ValueError(msg) + return values + + return _validator