marshmallow-code / marshmallow-code/marshmallow

Passing extra kwargs with ValidationError

Open
#833 0 comments 8 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
7.2k
Forks
738
Avg merge
1d 23h
Merged PRs (30d)
7

Description

Hi!
I'm trying to pass some extra data (e.g. error code) with (or inside) a ValidationError. My end goal is to have machine readable string constant (error code and error description) alongside marshmallow error message. For instance:

class Error(Enum):
    """Error codes."""

    MISSING_DATA = 'Missing data'
    FORBIDDEN_ENTITY = 'Forbidden data'

class MySchema(Schema):
    name = fields.Str()

    @validates_schema
    def validate_string(self, value):
        if value == 'forbidden_string':
            # raise error with specific code
            raise ValidationError('String is not allowed', error_code=Error.FORBIDDEN_ENTITY)

Later on, the error is to be serialized like this:

{
    'code': 'FORBIDDEN_ENTITY',  # Error.FORBIDDEN_ENTITY.name
    'title': 'Forbidden data',  # Error.FORBIDDEN_ENTITY.value
    'detail': 'String is not allowed',  # error message
    'source': {  # irrelevant here
        'pointer': '/data/attribures/name',
},

However, there are quite a few issues I run into:

  1. Some conditions are checked before custom validation take place. For example, when field is marked with required=True, or allow_none=False. Fortunately, in that case fail(self, key, **kwargs) is called, so it can be reimplemented to add custom code to the exception:
class BaseField(marshmallow.fields.Field):
    def fail(self, key, **kwargs):
        """Raises `marshmallow.ValidationError` with error code appended."""
        try:
            super().fail(key, **kwargs)
        except ValidationError as exc:
            error_code = get_error_code(key)  # get error code somehow
            exc.kwargs[f'error_code_{self.name}'] = key
            raise

self.name is used here to match error codes with field names when serializing the error.

  1. However, as far as I understand, there is no way to do the same trick in field validators since they have no information about a field they validate (slightly related to https://github.com/marshmallow-code/marshmallow/pull/709). Thus, an exception with custom error code can be raised but the information about the field it corresponds with is lost.

  2. Unmarshaller collects kwargs from all ValidationErrors raised, but only in schema- and field-level validators. kwargs in exceptions raised in pre_load and post_load functions are not propagated. Is this intended behavior?

  3. ValidationError raised in nested schemas are being caught and then reraised without kwargs. I believe https://github.com/marshmallow-code/marshmallow/blob/dev/marshmallow/fields.py#L474 is to blame. Is it intended? Although even if nested schemas do propagate kwargs, matching error codes with fields would be a huge pain...

Is there an easier way to achieve it?

My last resort would be to pick out an error code based on error message, but it will require a lot of fragile hardcoding.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading ValidationError, field validators, and Unmarshaller handling, then inspect marshmallow/fields.py around line 474. Trace how kwargs move through pre_load, post_load, schema-level, field-level, and nested validation errors. Done requires an agreed design and tests covering field association and kwargs propagation across these paths.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
api, backend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.