Around forms and APIs the problem of validating input data and serializing application data into simple data structures occurs on a regular base to me. marshmallow is a Python library which aims to solve this problem and appears to be pretty flexible.

I have evaluated it recently as a candidate in a Pyramid based project and came across the open questions about its internationalization support. In this post I am wrapping up my findings and showing a possible integration with translationstring.

Current handling of error messages in marshmallow

Currently error messages are stored as plain strings on the class level in an attribute called error_messages which is based on a class level attribute default_error_messages. On top of that it is possible to pass in custom messages when creating the instance of Field.

With Field.fail we see a utility method which raises an instance of ValidationError and calls msg.format if the error message is an instance of basestring.

The following pseudo Python code shows the principle:

message = self.error_messages[message_key]

if isinstance(message, basestring):
       message = message.format(**kwargs)

Challenge when using translationstring

The package translationstring is commonly used in Pyramid based projects, its class translationstring.TranslationString is representing a translatable string.

Since the class is a subclass of str, the call to format() will change it into a regular string based on the default value. This basically stops translation from working in this particular case.

Specialized MarshmallowTranslationString

We know that marshmallow will apply the parameters via a call to format(). This allows us to come up with a specialized subclass of TranslationString. This class would override format() and update the mapping of the translatable string accordingly. The result of the call to format() would be another instance of this specialized class.

The following example shows how this class might look like:

class FormatAwareTranslationString(translationstring.TranslationString):

    def format(self, **kw):
        if self.mapping:
            mapping = self.mapping.copy()
            mapping.update(kw)
        else:
            mapping = kw.copy()
        return FormatAwareTranslationString(self, mapping=mapping)

Usage in a schema

The following code snippet shows how this class could be used in a marshmallow schema:

class ExampleSchema(Schema):
    my_attribute = fields.String(
        required=True,
        error_messages={
            'required': FormatAwareTranslationString("This value is required.")
        })

Conclusion

First of all, this approach allows to use translationstring together with marshmallow, which was the aim of this investigation. I will probably use this approach until I come up with a better solution, especially for applications which use translationstring.

There is a clear drawback though: This solution is tightly coupled to the internals of marshmallow. It works because I did assume that every error message will be processed via a call to format(), and that only keyword parameters will be passed into this call. This is in my opinion a strong indicator suggesting to look for a better approach. Ideally one which could separate the concerns of field validation and internationalization.

One approach might be to "change" the method Field.fail() or make it configurable, so that an application specific implementation could be plugged in.

Another option which I see is to look at a lazy version of gettext(), similar to the way how Django is doing translations. I assume though that this would depend on some global state being available to grab the current user's locale once the evaluation happens.


Comments

comments powered by Disqus