Open
Description
Consider this serializer:
class PasswordChangeSerializer(serializers.Serializer):
old_password = serializers.CharField(max_length=128)
new_password1 = serializers.CharField(max_length=128)
new_password2 = serializers.CharField(max_length=128)
set_password_form: SetPasswordForm
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = cast(Request, self.context.get("request"))
self.user = cast(User, getattr(self.request, "user"))
def validate_old_password(self, value):
if not self.user.check_password(value):
raise serializers.ValidationError("Your old password was entered incorrectly. Please enter it again.")
return value
def validate(self, attrs):
self.set_password_form = SetPasswordForm(
user=self.user,
data=attrs,
)
if not self.set_password_form.is_valid():
raise serializers.ValidationError(self.set_password_form.errors)
return attrs
def save(self, **kwargs):
self.set_password_form.save()
update_session_auth_hash(self.request, self.user)
This is all perfectly fine code, but mypy is complaining here: Argument 1 to "ValidationError" has incompatible type "ErrorDict"; expected "_APIExceptionInput"
.
Here's the actual class from DRF:
class ValidationError(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = _('Invalid input.')
default_code = 'invalid'
def __init__(self, detail=None, code=None):
if detail is None:
detail = self.default_detail
if code is None:
code = self.default_code
# For validation failures, we may collect many errors together,
# so the details should always be coerced to a list if not already.
if isinstance(detail, tuple):
detail = list(detail)
elif not isinstance(detail, dict) and not isinstance(detail, list):
detail = [detail]
self.detail = _get_error_details(detail, code)
And the implementation of _get_error_details
:
def _get_error_details(data, default_code=None):
"""
Descend into a nested data structure, forcing any
lazy translation strings or strings into `ErrorDetail`.
"""
if isinstance(data, (list, tuple)):
ret = [
_get_error_details(item, default_code) for item in data
]
if isinstance(data, ReturnList):
return ReturnList(ret, serializer=data.serializer)
return ret
elif isinstance(data, dict):
ret = {
key: _get_error_details(value, default_code)
for key, value in data.items()
}
if isinstance(data, ReturnDict):
return ReturnDict(ret, serializer=data.serializer)
return ret
text = force_str(data)
code = getattr(data, 'code', default_code)
return ErrorDetail(text, code)
As you can see, a dict is perfectly fine here, so it seems that the types from djangorestframework-stubs are too restrictive? I'm using djangorestframework-stubs[compatible-mypy]>=3.15.2
.
Passing in self.set_password_form.errors.as_data()
didn't solve the typing error either.