boardpack/pydantic-i18n

Problems with enum validation messages

pyritewolf opened this issue ยท 4 comments

Describe the bug

I have a schema that checks various fields against enums. When I feed it an invalid value in one of those fields, I get the following traceback:

api2_1             | Traceback (most recent call last):
api2_1             |   File "/usr/local/lib/python3.8/site-packages/uvicorn/protocols/http/httptools_impl.py", line 385, in run_asgi
api2_1             |     result = await app(self.scope, self.receive, self.send)
api2_1             |   File "/usr/local/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
api2_1             |     return await self.app(scope, receive, send)
api2_1             |   File "/usr/local/lib/python3.8/site-packages/fastapi/applications.py", line 146, in __call__
api2_1             |     await super().__call__(scope, receive, send)
api2_1             |   File "/usr/local/lib/python3.8/site-packages/starlette/applications.py", line 102, in __call__
api2_1             |     await self.middleware_stack(scope, receive, send)
api2_1             |   File "/usr/local/lib/python3.8/site-packages/starlette/middleware/errors.py", line 181, in __call__
api2_1             |     raise exc from None
api2_1             |   File "/usr/local/lib/python3.8/site-packages/starlette/middleware/errors.py", line 159, in __call__
api2_1             |     await self.app(scope, receive, _send)
api2_1             |   File "/usr/local/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
api2_1             |     return await self.app(scope, receive, send)
api2_1             |   File "/usr/local/lib/python3.8/site-packages/starlette/middleware/cors.py", line 84, in __call__
api2_1             |     await self.simple_response(scope, receive, send, request_headers=headers)
api2_1             |   File "/usr/local/lib/python3.8/site-packages/starlette/middleware/cors.py", line 140, in simple_response
api2_1             |     await self.app(scope, receive, send)
api2_1             |   File "/usr/local/lib/python3.8/site-packages/starlette/exceptions.py", line 82, in __call__
api2_1             |     raise exc from None
api2_1             |   File "/usr/local/lib/python3.8/site-packages/starlette/exceptions.py", line 71, in __call__
api2_1             |     await self.app(scope, receive, sender)
api2_1             |   File "/usr/local/lib/python3.8/site-packages/starlette/routing.py", line 550, in __call__
api2_1             |     await route.handle(scope, receive, send)
api2_1             |   File "/usr/local/lib/python3.8/site-packages/starlette/routing.py", line 227, in handle
api2_1             |     await self.app(scope, receive, send)
api2_1             |   File "/usr/local/lib/python3.8/site-packages/starlette/routing.py", line 41, in app
api2_1             |     response = await func(request)
api2_1             |   File "/usr/local/lib/python3.8/site-packages/fastapi/routing.py", line 196, in app
api2_1             |     raw_response = await run_endpoint_function(
api2_1             |   File "/usr/local/lib/python3.8/site-packages/fastapi/routing.py", line 150, in run_endpoint_function
api2_1             |     return await run_in_threadpool(dependant.call, **values)
api2_1             |   File "/usr/local/lib/python3.8/site-packages/starlette/concurrency.py", line 34, in run_in_threadpool
api2_1             |     return await loop.run_in_executor(None, func, *args)
api2_1             |   File "/usr/local/lib/python3.8/concurrent/futures/thread.py", line 57, in run
api2_1             |     result = self.fn(*self.args, **self.kwargs)
api2_1             |   File "./studies/router.py", line 73, in upload_studies_with_csv
api2_1             |     return controller.crud.upload_studies_with_csv(
api2_1             |   File "./studies/controller.py", line 356, in upload_studies_with_csv
api2_1             |     all_errors.append(_csv_line_error(idx, tr.translate(e.errors(), locale="es_AR").json(), row))
api2_1             |   File "/usr/local/lib/python3.8/site-packages/pydantic_i18n/main.py", line 25, in translate
api2_1             |     return [
api2_1             |   File "/usr/local/lib/python3.8/site-packages/pydantic_i18n/main.py", line 28, in <listcomp>
api2_1             |     "msg": self.source.gettext(error["msg"], locale),
api2_1             |   File "/usr/local/lib/python3.8/site-packages/pydantic_i18n/loaders.py", line 29, in gettext
api2_1             |     return data[key]
api2_1             | KeyError: "value is not a valid enumeration member; permitted: '9_to_12', '12_to_15', '14_to_18'"

The KeyError is to be expected, because i'm not getting anything similar when I ask for the full dict of possible translations. I've linted this response for ease of reading, otherwise it's as is from the method call:

>>> print(PydanticI18n.get_pydantic_messages())
{
      'field required': 'field required',
      'extra fields not permitted': 'extra fields not permitted',
      'none is not an allowed value': 'none is not an allowed value',
      'value is not none': 'value is not none',
      'value is not None': 'value is not None',
      'value could not be parsed to a boolean': 'value could not be parsed to a boolean',
      'byte type expected': 'byte type expected',
      'value is not a valid dict': 'value is not a valid dict',
      'value is not a valid email address': 'value is not a valid email address',
      'invalid or missing URL scheme': 'invalid or missing URL scheme',
      'URL scheme not permitted': 'URL scheme not permitted',
      'userinfo required in URL but missing': 'userinfo required in URL but missing',
      'URL host invalid': 'URL host invalid',
      'URL host invalid, top level domain required': 'URL host invalid, top level domain required',
      'URL port invalid, port cannot exceed 65535': 'URL port invalid, port cannot exceed 65535',
      'URL invalid, extra characters found after valid URL: {extra!r}': 'URL invalid, extra characters found after valid URL: {extra!r}',
      '{value} is not a valid Enum instance': '{value} is not a valid Enum instance',
      '{value} is not a valid IntEnum instance': '{value} is not a valid IntEnum instance',
      'value is not a valid integer': 'value is not a valid integer',
      'value is not a valid float': 'value is not a valid float',
      'value is not a valid path': 'value is not a valid path',
      'file or directory at path "{path}" does not exist': 'file or directory at path "{path}" does not exist',
      'path "{path}" does not point to a file': 'path "{path}" does not point to a file',
      'path "{path}" does not point to a directory': 'path "{path}" does not point to a directory',
      'ensure this value contains valid import path or valid callable: {error_message}': 'ensure this value contains valid import path or valid callable: {error_message}',
      'value is not a valid sequence': 'value is not a valid sequence',
      'value is not a valid list': 'value is not a valid list',
      'value is not a valid set': 'value is not a valid set',
      'value is not a valid frozenset': 'value is not a valid frozenset',
      'value is not a valid tuple': 'value is not a valid tuple',
      'wrong tuple length {actual_length}, expected {expected_length}': 'wrong tuple length {actual_length}, expected {expected_length}',
      'ensure this value has at least {limit_value} items': 'ensure this value has at least {limit_value} items',
      'ensure this value has at most {limit_value} items': 'ensure this value has at most {limit_value} items',
      'ensure this value has at least {limit_value} characters': 'ensure this value has at least {limit_value} characters',
      'ensure this value has at most {limit_value} characters': 'ensure this value has at most {limit_value} characters',
      'str type expected': 'str type expected',
      'string does not match regex "{pattern}"': 'string does not match regex "{pattern}"',
      'ensure this value is greater than {limit_value}': 'ensure this value is greater than {limit_value}',
      'ensure this value is greater than or equal to {limit_value}': 'ensure this value is greater than or equal to {limit_value}',
      'ensure this value is less than {limit_value}': 'ensure this value is less than {limit_value}',
      'ensure this value is less than or equal to {limit_value}': 'ensure this value is less than or equal to {limit_value}',
      'ensure this value is a multiple of {multiple_of}': 'ensure this value is a multiple of {multiple_of}',
      'value is not a valid decimal': 'value is not a valid decimal',
      'ensure that there are no more than {max_digits} digits in total': 'ensure that there are no more than {max_digits} digits in total',
      'ensure that there are no more than {decimal_places} decimal places': 'ensure that there are no more than {decimal_places} decimal places',
      'ensure that there are no more than {whole_digits} digits before the decimal point': 'ensure that there are no more than {whole_digits} digits before the decimal point',
      'invalid datetime format': 'invalid datetime format',
      'invalid date format': 'invalid date format',
      'invalid time format': 'invalid time format',
      'invalid duration format': 'invalid duration format',
      'value is not a valid hashable': 'value is not a valid hashable',
      'value is not a valid uuid': 'value is not a valid uuid',
      'uuid version {required_version} expected': 'uuid version {required_version} expected',
      'instance of {expected_arbitrary_type} expected': 'instance of {expected_arbitrary_type} expected',
      'a class is expected': 'a class is expected',
      'subclass of {expected_class} expected': 'subclass of {expected_class} expected',
      'Invalid JSON': 'Invalid JSON',
      'JSON object must be str, bytes or bytearray': 'JSON object must be str, bytes or bytearray',
      'Invalid regular expression': 'Invalid regular expression',
      'instance of {class_name}, tuple or dict expected': 'instance of {class_name}, tuple or dict expected',
      '{value} is not callable': '{value} is not callable',
      'value is not a valid IPv4 or IPv6 address': 'value is not a valid IPv4 or IPv6 address',
      'value is not a valid IPv4 or IPv6 interface': 'value is not a valid IPv4 or IPv6 interface',
      'value is not a valid IPv4 or IPv6 network': 'value is not a valid IPv4 or IPv6 network',
      'value is not a valid IPv4 address': 'value is not a valid IPv4 address',
      'value is not a valid IPv6 address': 'value is not a valid IPv6 address',
      'value is not a valid IPv4 network': 'value is not a valid IPv4 network',
      'value is not a valid IPv6 network': 'value is not a valid IPv6 network',
      'value is not a valid IPv4 interface': 'value is not a valid IPv4 interface',
      'value is not a valid IPv6 interface': 'value is not a valid IPv6 interface',
      'value is not a valid color: {reason}': 'value is not a valid color: {reason}',
      'value is not a valid boolean': 'value is not a valid boolean',
      'card number is not all digits': 'card number is not all digits',
      'card number is not luhn valid': 'card number is not luhn valid',
      'Length for a {brand} card must be {required_length}': 'Length for a {brand} card must be {required_length}',
      'could not parse value and unit from byte string': 'could not parse value and unit from byte string',
      'could not interpret byte unit: {unit}': 'could not interpret byte unit: {unit}'
}

I've tried adding a line in the spirit of "value is not a valid enumeration member; permitted: {permitted}": "value is not a valid enumeration member; permitted: {permitted}", both in my english and my translation dict, but that didn't work either. I hope I'm missing something silly in my config, because this package looks really handy for what we're doing! ๐Ÿ˜„

To Reproduce

Here's a little script that reproduces the issue:

from enum import Enum
from pydantic import BaseModel, ValidationError
from pydantic_i18n import PydanticI18n

class ACoolEnum(Enum):
    NINE_TO_TWELVE = "9_to_12"
    TWELVE_TO_FIFTEEN = "12_to_15"
    FOURTEEN_TO_EIGHTEEN = "14_to_18"

class CoolSchema(BaseModel):
    enum_field: ACoolEnum


translations = {
    "en_US": {
      "value is not a valid enumeration member; permitted: {permitted}": "value is not a valid enumeration member; permitted: {permitted}",
    },
    "es_AR": {
        "value is not a valid enumeration member; permitted: {permitted}": "el valor no es uno de los valores permitidos, que son: {permitted}",
    },
}

tr = PydanticI18n(translations)
CoolSchema(enum_field="9_to_12")
print("passed a valid value just fine!")

try:
    CoolSchema(enum_field="super invalid value")
except ValidationError as e:
    translated_errors = tr.translate(e.errors(), locale="es_AR")

Environment

  • OS: macOS
  • pydantic-i18n version: 0.1.1
  • Python version: 3.8.2

Hello @pyritewolf. Thank you very much for your report, I will check it tomorrow ๐Ÿ™‚

@pyritewoluf currently, unfortunately, messages with placeholders aren't supported by pydantic-i18n ๐Ÿ˜• I'll work more on it in the near several days.

Hey @pyritewolf, you can try a new 0.2 version of pydantic-i18n ๐Ÿ˜ƒ

@pyritewolf I close this issue, if you get any other bug, your issue or PR will be helpful, thanks!