cerberus
cerberus copied to clipboard
Custom error messages
Could not find anywhere in the docs how I can define custom validation error messages, so I'm assuming that this is not implemented. Please link me to the doc page if I'm mistaken.
I think this is a really good functionality, specially if you want to use cerberus to validate request structure and don't want to process the outcome of the errors dict any further and wish to create beautiful error messages for your response.
do you want custom messages for contributed or for custom rules? am i understanding correctly that you want a string as result?
oh, and are humans the intended audience?
Yes, I want strings and humans are the intended audience.
If I get a error validating a string field according to a regex rule I get the following error:
{
"name": "value does not match regex '^[a-zA-Z0-9]$'"
}
I'd like to be able to assign a custom message in case the field 'name' is not valid. Something along the lines of:
{
"name": "value must only contain letters and digits"
}
a HumanReadableErrorHandler
you could subclass and override error messages is on my agenda (#93).
but i want to implement XML, JSON and YAML-output next; slowly but steadily evolving the underlying mechanics.
unfortunately i can't give you a reliable estimation atm. it all might happen this year or at the beginning of the next. but several other projects need my attention.
you have an interesting example here as the error message is depending on the full constraint, not only the rule.
+1
Also it is required for non-english projects, like this:
{
"name": lazy_gettext("It seems that the user name is too complicated! Maybe you try something simpler?")
}
# => "Кажется, имя пользователя слишком сложное! Может, попробуете что-нибудь попроще?"
I found a temporary workaround for 0.10:
import cerberus
class CustomErrorHandler(cerberus.errors.BasicErrorHandler):
def __init__(self, tree=None, custom_messages=None):
super(CustomErrorHandler, self).__init__(tree)
self.custom_messages = custom_messages or {}
def format_message(self, field, error):
tmp = self.custom_messages
for x in error.schema_path:
try:
tmp = tmp[x]
except KeyError:
return super(CustomErrorHandler, self).format_message(field, error)
if isinstance(tmp, dict): # if "unknown field"
return super(CustomErrorHandler, self).format_message(field, error)
else:
return tmp
v = cerberus.Validator(
{'username': {'minlength': 2, 'regex': r'[A-z0-9]+'}},
error_handler=CustomErrorHandler(custom_messages={'username': {'regex': u'Не мудрите'}})
)
print(v.validate({'username': '⌀'})) # => False
print(v.errors) # => {'username': ['min length is 2', 'Не мудрите']}
(actually 'max length is 2' because #209 :)
+1 this seems like a very practical feature!
@Pithikos fyi: https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments
I was looking for i10n too but it seems it's not on docs, it would be great to add them.
@funkyfuture I feel like your work in #93 is hidden under code, add it to docs and make it shine :)
to clarify:
- a
HumanReadableErrorHandler
is not about the localization of theerrors.BasicErrorHandler
, but about an errorhandler that returns a list of prose in strings, including l10n - that error handler would live here
- ~there's only a reference xml-error handler in that collection on my disk yet~
- i didn't plan to release anything of the collections before the 1.0 of cerberus
@k1-hedayati would you be interested in contributing? having a non-latin-alphabet in the mix from the beginning would certainly be useful. is Farsi a rtl-language?
I didn't notice about 1.0, I thought your code is already on stable branch.
@funkyfuture yes I will be happy to contribute on this, how can I help? Yes Farsi and Arabic both are rtl
@k1-hedayati i've updated the mentioned repo, so one should get an idea what the whole thing is about. feel free to open a related issue there. any contribution regarding design proposals, implementation, tests or translations (workflows) is welcome.
Are custom error messages implemented yet?
This would be a great feature.
I have a multi-lingual app, and want to show messages such as 'Please enter your e-mail' or 'S'il vous plaît entrer votre e-mail' instead of a static 'required field'.
Thanks for your hard work on this library.
malheureusement, non. i just can point you at this issue: https://github.com/funkyfuture/cerberus-collections/issues/2 (just updated some bits). i have no capacities to join in actively, but certainly will manage to review and release.
more complex error handlers will not find the way into cerberus
in order to not depend on these in the release cycle.
@andreymal, you don't necessarily need to override the format_message
method: https://stackoverflow.com/q/47730454/2489914
cc @Arion-Dsh @nykolaslima
@funkyfuture what if I wish to set different messages for different data types? For example, "Username is invalid" for username field and "Email is invalid" for email field. In the same validator of course
yeah, then you have to tinker around. i related my comment to the i18n part that came up.
@funkyfuture actually I combine both methods in production code :) https://pastebin.com/fkhZvBGr
Any plan to implement internationalization?
I think it could be a good feature. Same as @leebenson, I want to show error messages in different languages.
I thought localization could be implemented like jQuery validation as follow: https://github.com/jquery-validation/jquery-validation/tree/master/src/localization
Instead of have multiple files, cerberus could have a module called localization.py and we could set as follow:
v = Validator(schema, lang="pt-BR")
Does anyone agree?
@rgercp imho, simple and universal lazy_gettext
is a better solution
If you don't want to mess with the error handler and your app doesn't need language features anyway, you can make a custom regex validator with the error message as a part of the argument for your custom validator
from cerberus import Validator
import re
class MyCustomValidator(Validator):
def _validate_lazyregex(self, lazyregex_options, field, value):
""" {'type': 'dict'} """
pattern = lazyregex_options['pattern']
fail_message = lazyregex_options['fail_message']
if not isinstance(value, _str_type):
return
if not pattern.endswith('$'):
pattern += '$'
re_obj = re.compile(pattern)
if not re_obj.match(value):
self._error(field, fail_message)
schema = {
'email': {
'type': 'string',
'required': True,
'lazyregex': {
'pattern': '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$',
'fail_message': 'Doesn\'t look like an email to me, man'
}
}
}
This is not the recommended approach, but just another option for the people who stumble upon this problem in the future.
@nicolaiarocci @andreymal unable to find documentation for the custom error messages.
I tried the slightly modified version of the solution given below and it works with simple documents it fails when using complex docs. stackoverflow Modified version
class CustomErrorHandler(BasicErrorHandler):
def __init__(self, schema):
self.custom_defined_schema = schema
def _format_message(self, field, error):
error_msg = self.custom_defined_schema[field].get('meta', {}).get('_errors', {}).get(error.rule)
if not error_msg:
return error_msg
return super(CustomErrorHandler, self)._format_message(field, error)
It feels like a workaround and also that's a lot of work just to give an alternate message.
@funkyfuture I am trying to keep the custom error messages for certain fields in the meta tag. However, I am not able to access this meta tag for inner elements. Example, if a key is in a nested dictionary, then the below line of code does not allow me to access the meta tag. Can you suggest me a better way to handle this? error_msg = self.custom_defined_schema[field].get('meta', {}).get('error_message', field)
My CustomErorHandler has been attached below. I am trying to get meta tags for specific types of errors and throw them.
class CustomErrorHandler(BasicErrorHandler):
def __init__(self, schema=None, tree=None):
super().__init__()
self.custom_defined_schema = schema
if tree is not None:
self.tree = tree
def _format_message(self, field, error):
logger.error("Field- " + str(field) + " | " + "Value- " + str(error.value) + " | " + "Error- " + str(
super(CustomErrorHandler, self)._format_message(field, error)))
print(error.value, "---", error.code)
handle_error_fields = [0, 2, 36, 37, 66, 68]
if error.code == 3:
return "Field passed -" + field + " is not present in master. Please contact admin!"
if error.code in handle_error_fields:
return super(CustomErrorHandler, self)._format_message(field, error)
error_message_found = False
try:
error_msg = self.custom_defined_schema[field].get('meta', {}).get('error_message', field)
error_message_found = True
except KeyError:
pass
if error_message_found == False:
error_msg = self.custom_defined_schema[field].get('meta', {}).get('error_message', field)
if error_msg == field:
return super(CustomErrorHandler, self)._format_message(field, error)
return error_msg
Can you suggest me a better way to handle this?
not really from a quick glance and no knowledge of the use-case (and i'm not interested in solving it), but it could eventually be easier to not base on the BasicErrorHandler
. instead implement a custom error handler that suits your exact requirements.
error_msg = self.custom_defined_schema[field].get('meta', {}).get('error_message', field)
should that secong call to get
return the field name as default?
Have figured it out @funkyfuture . Thanks. Had to use the schema_path to get the meta field
@Abhisek1994Roy can you give some example how you did it
I had the same problem. I prepared a tool to customize error messages. If you need, please don't hesitate to use Cerberror.
@pbrus , out of curiosity, why don't you use the ErrorHandler API?
@funkyfuture, because I don't need it and it seems to be somehow limited. Let me explain this.
If I understood your post correctly, inheriting from BasicErrorHandler
allows to define a new error message1. Cerberror doesn't need these messages. The translate
method matches defined messages with paths2. This is happening outside the validator of Cerberus
. There is no sense to put these messages into errors.BasicErrorHandler.messages
container using API and get them back in the next step to, finally, assign them to the paths. This is the first reason why I omit ErrorHandler
API.
The second reason is caused by the limitation of API. Using it we are able to define new messages globally. Let's say that we need to define two fields with regex rules (code 65). The first one is a name of a user which must start with an upper-case letter. The second field is to store an email address. Using API we can set one error message for these fields. For me the better approach is:
('user', 'name') 65 "The name {{value}} must start with upper-case letter"
('user', 'email') 65 "The address email must contain @ sign"
This is what @ha1zum achieved partially. But this still doesn't solve a problem with unclear paths.
If we consider single fields we can easy handle them, modify them and so on. But the problems occur when we play with many nested fields. The definition of a schema is growing fast and I think that playing with a code is harder than just defining a file with paths and messages. And that is why I created Cerberror.
1 IMHO such code example should be in the Errors section.
2 I prefer to name the method translate
instead of match
because as an interface the first one should be more intuitive describing what the method does.