Skip to content

Commit

Permalink
Merge pull request #1067 from Dunstrom/#852-configurable-error-messages
Browse files Browse the repository at this point in the history
#852 configurable error messages
  • Loading branch information
sloria authored Jan 8, 2019
2 parents 4dc6b65 + 591778e commit b3bef3c
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 6 deletions.
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,5 @@ Contributors (chronological)
- Jan Margeta `@jmargeta <https://github.com/jmargeta>`_
- AlexV `@asmodehn <https://github.com/asmodehn>`_
- `@toffan <https://github.com/toffan>`_
- Hampus Dunström `@Dunstrom <https://github.com/Dunstrom>`_
- Robert Jensen `@r1b <https://github.com/r1b>`_
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Changelog

Features:

- Make the error messages for "unknown fields" and "invalid data type"
configurable (:issue:`852`). Thanks :user:`Dunstrom` for the PR.
- ``fields.Boolean`` parses ``"yes"``/``"no"`` values (:pr:`1081`).
Thanks :user:`r1b`.

Expand Down
2 changes: 0 additions & 2 deletions docs/api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ Schema

.. autoclass:: marshmallow.SchemaOpts

.. autofunction:: marshmallow.pprint

.. _api_fields:

Fields
Expand Down
14 changes: 14 additions & 0 deletions docs/extending.rst
Original file line number Diff line number Diff line change
Expand Up @@ -462,3 +462,17 @@ The ``context`` attribute of a `Schema` is a general-purpose store for extra inf
# custom fields, schema methods, schema validators, etc.
schema.context['request'] = request
schema.dump(user)
Custom Error Messages
---------------------

You can customize the error messages that `dump <marshmallow.Schema.dump>` and `dumps <marshmallow.Schema.dumps>` uses when raising a `ValidationError <marshmallow.exceptions.ValidationError>`.
You do this by overriding the ``error_messages`` class variable:

.. code-block:: python
class MySchema(Schema):
error_messages = {
'unknown': 'Custom unknown field error message.',
'type': 'Custom invalid type error message.'
}
2 changes: 1 addition & 1 deletion marshmallow/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class Field(FieldABC):

#: Default error messages for various kinds of errors. The keys in this dictionary
#: are passed to `Field.fail`. The values are error messages passed to
#: :exc:`marshmallow.ValidationError`.
#: :exc:`marshmallow.exceptions.ValidationError`.
default_error_messages = {
'required': 'Missing data for required field.',
'null': 'Field may not be null.',
Expand Down
19 changes: 16 additions & 3 deletions marshmallow/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,13 @@ class Meta:
dt.timedelta: ma_fields.TimeDelta,
decimal.Decimal: ma_fields.Decimal,
}
#: Overrides for default schema-level error messages
error_messages = {}

_default_error_messages = {
'type': 'Invalid input type.',
'unknown': 'Unknown field.',
}

OPTIONS_CLASS = SchemaOpts

Expand Down Expand Up @@ -360,6 +367,12 @@ def __init__(
self._normalize_nested_options()
#: Dictionary mapping field_names -> :class:`Field` objects
self.fields = self._init_fields()
messages = {}
messages.update(self._default_error_messages)
for cls in reversed(self.__class__.__mro__):
messages.update(getattr(cls, 'error_messages', {}))
messages.update(self.error_messages or {})
self.error_messages = messages

def __repr__(self):
return '<{ClassName}(many={self.many})>'.format(
Expand Down Expand Up @@ -590,7 +603,7 @@ def _deserialize(
index = index if index_errors else None
if many:
if not is_collection(data):
error_store.store_error(['Invalid input type.'], index=index)
error_store.store_error([self.error_messages['type']], index=index)
ret = []
else:
self._pending = True
Expand All @@ -608,7 +621,7 @@ def _deserialize(
ret = dict_class()
# Check data is a dict
if not isinstance(data, Mapping):
error_store.store_error(['Invalid input type.'], index=index)
error_store.store_error([self.error_messages['type']], index=index)
else:
partial_is_collection = is_collection(partial)
for attr_name, field_obj in iteritems(fields_dict):
Expand Down Expand Up @@ -662,7 +675,7 @@ def _deserialize(
set_value(ret, key, value)
elif unknown == RAISE:
error_store.store_error(
['Unknown field.'],
[self.error_messages['unknown']],
key,
(index if index_errors else None),
)
Expand Down
80 changes: 80 additions & 0 deletions tests/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,86 @@ class ErrorSchema(Schema):
assert 'Invalid email' in errors['email']


def test_custom_unknown_error_message():
custom_message = 'custom error message.'

class ErrorSchema(Schema):
error_messages = {'unknown': custom_message}
name = fields.String()

s = ErrorSchema()
u = {'name': 'Joe', 'age': 13}
with pytest.raises(ValidationError) as excinfo:
s.load(u)
errors = excinfo.value.messages
assert custom_message in errors['age']


def test_custom_type_error_message():
custom_message = 'custom error message.'

class ErrorSchema(Schema):
error_messages = {'type': custom_message}
name = fields.String()

s = ErrorSchema()
u = ['Joe']
with pytest.raises(ValidationError) as excinfo:
s.load(u)
errors = excinfo.value.messages
assert custom_message in errors['_schema']


def test_custom_type_error_message_with_many():
custom_message = 'custom error message.'

class ErrorSchema(Schema):
error_messages = {'type': custom_message}
name = fields.String()

s = ErrorSchema(many=True)
u = {'name': 'Joe'}
with pytest.raises(ValidationError) as excinfo:
s.load(u)
errors = excinfo.value.messages
assert custom_message in errors['_schema']


def test_custom_error_messages_with_inheritance():
parent_type_message = 'parent type error message.'
parent_unknown_message = 'parent unknown error message.'
child_type_message = 'child type error message.'

class ParentSchema(Schema):
error_messages = {
'type': parent_type_message,
'unknown': parent_unknown_message,
}
name = fields.String()

class ChildSchema(ParentSchema):
error_messages = {'type': child_type_message}

unknown_user = {'name': 'Eleven', 'age': 12}
type_user = 11

parent_schema = ParentSchema()
with pytest.raises(ValidationError) as excinfo:
parent_schema.load(unknown_user)
assert parent_unknown_message in excinfo.value.messages['age']
with pytest.raises(ValidationError) as excinfo:
parent_schema.load(type_user)
assert parent_type_message in excinfo.value.messages['_schema']

child_schema = ChildSchema()
with pytest.raises(ValidationError) as excinfo:
child_schema.load(unknown_user)
assert parent_unknown_message in excinfo.value.messages['age']
with pytest.raises(ValidationError) as excinfo:
child_schema.load(type_user)
assert child_type_message in excinfo.value.messages['_schema']


def test_load_errors_with_many():
class ErrorSchema(Schema):
email = fields.Email()
Expand Down

0 comments on commit b3bef3c

Please sign in to comment.