from typing import Type, Tuple from marshmallow import post_load, pre_load, ValidationError, fields, validate, validates_schema from apollo_utils.core.constants import ALL, CLIENTS_TYPES, CLIENT_PORTAL, SpotifyListType from apollo_utils.core.utils.ext_enum import ExtEnum from apollo_utils.service.schemas.fields.list import SplitUniqueListField class HandleAllMixin: @post_load def handle_all(self, data: dict, **kwargs): if not hasattr(self, "fields_with_all"): raise ValueError() for field_key, field_enum in self.fields_with_all.items(): if ALL in data[field_key]: data[field_key] = field_enum.values() return data class OneIdMixin: _id_fields = {"spotify_id", "apple_id"} @pre_load def validate_single_argument(self, data): """Ensure that exactly one ID is provided.""" keys = self._id_fields & set(data.keys()) if len(keys) != 1: raise ValidationError("Provide one and only one ID of {}.".format(", ".join(self._id_fields))) return data def get_int_id_list_mixin(min_len=1, max_len=100): class IntIdListMixin: id_list = SplitUniqueListField(fields.Int(required=True), data_key="id", validate=validate.Length(min=min_len, max=max_len)) return IntIdListMixin class ClientTypeMixin: client_type = fields.String(validate=validate.OneOf(CLIENTS_TYPES), missing=CLIENT_PORTAL) class ListTypeMixin: list_type = fields.String(validate=validate.OneOf(SpotifyListType.ALL), missing=SpotifyListType.REGIONAL) def get_dependant_mixin( validate_field: str, depends_on_field: str, always_allowed: list = None, enum: Type[ExtEnum] = None, depends_on_enum: Type[ExtEnum] = None, depends_mapping: dict = None, ) -> type: """Generates mixin to validate fields dependencies. Like order_by and include, you need to include something to order by it. It gets result field value from original field by enum values (depends_on_enum is set) or by mapping (depends_mapping is set). Args: validate_field: A field (name) to validate. depends_on_field: A field (name) that it depends on. always_allowed: A list of always allowed values, ignored values. enum: Validating field type. depends_on_enum: Depends on field type. depends_mapping: Mapping of validate field enum values to depends on field enum values. """ class DependantMixin: @validates_schema def validate_dependant_fields(self, data, **kwargs): validate_values = data.get(validate_field) if not isinstance(validate_values, list): validate_values = [validate_values] if always_allowed: validate_values = set(validate_values) - set(always_allowed) if not validate_values: return depends_on_values = data.get(depends_on_field) for value in validate_values: if isinstance(value, str): value = enum(value) if depends_on_enum: depends_item = depends_on_enum(value.value) elif value in depends_mapping: depends_item = depends_mapping[value] else: continue if depends_item not in depends_on_values and depends_item.value not in depends_on_values: raise ValidationError("Include a field to sort by it.") return DependantMixin def get_only_one_field_mixin(check_fields: Tuple[str, ...]): """Get Mixin to check if one and only one field is passed Args: check_fields: fields to be checked """ class OnlyOneFieldMixin: _fields = set(check_fields) @post_load def validate_single_argument(self, data, *args, **kwargs): """Ensure that exactly one field is provided.""" keys = self._fields & set(data.keys()) if len(keys) != 1: raise ValidationError("Provide one and only one ID of {}.".format(", ".join(self._fields))) return data return OnlyOneFieldMixin