[Freeipa-devel] parameter validation

John Dennis jdennis at redhat.com
Thu Apr 5 21:55:58 UTC 2012


Jason was exceptional at documenting everything he wrote, especially in 
the framework. However the correct use of validation in the Param class 
is not discussed at all. I tried to figure it out by looking at what we 
currently do in our code base. Given the inconsistencies I presume I'm 
not the only one who wasn't quite sure how to do this right (or rather 
what the intent was). Below are some issues/questions, if you can shed 
light on any them it would be helpful and I'll try to summarize and get 
best practice documented.

Every param object is passed a name and then zero or more validation 
functions as positional arguments. Each validation function has this 
signature:

validate(ugettext, value)

It's supposed to return None if there was no validation error. If there 
was a validation error it's supposed to return an error string.

1) Most of our validation functions do not follow the calling convention 
in parameter.py for a validator. Instead of returning either None or an 
error string they raise a ValidationError.

Here is an example of how the code in parameter.py calls a validation 
function:

         for rule in self.all_rules:
             error = rule(ugettext, value)
             if error is not None:
                 raise ValidationError(
                     name=self.get_param_name(),
                     value=value,
                     index=index,
                     error=error,
                     rule=rule,
                 )

But there are a number of places in the code where we raise a 
ValidationError and substitute a hardcoded name. Directly raising a 
ValidationError by-passes the above and prevents correctly filling in 
all the information. Speaking of all the information see the next point.

The direct use of ValidationError occurs in other places besides 
validation functions. There are places where that makes sense, perhaps 
the error only occurs in the presence of another option, or perhaps it's 
buried deep in some other code, but for validation functions that can 
validate without external context we shouldn't be raising 
ValidationError directly, but rather returning an error string, right?

2) Currently ValidationError only returns this string:

     format = _('invalid %(name)r: %(error)s')

Note only the name and error keywords are used, omitted are value, index 
and rule. Shouldn't the ValidationError test for presence of the other 
keywords and return a different format string including those keywords 
if they are present? Seems to me we're dropping useful information which 
would be helpful to the user on the floor.

3) I'm confused as to why the validator signature has ugettext as a 
parameter. None of our code uses it. I suspect the intent was to use it 
to obtain the l10n translation for the error string. But as far as I can 
tell the i18n strings used in the validators via _().

# current usage, believed to be correct
validate(ugettext, value)
     return _("Must not contain spaces")

But if the validator used the ugettext parameter, it would have to be 
done in one of two fashions

# This does not work because the string is not marked for translation 
via _(), ugettext will never find the string.
validate(ugettext, value)
     return ugettext("Must not contain spaces")

# This is just silly, the _() returns the l10n translation (by calling
# ugettext), which is then passed to ugettext a second time which does
# nothing because it can't find the translation for a string already
# translated, it's a no-op.
validate(ugettext, value)
     return ugettext(_("Must not contain spaces"))

Also, by passing only a pointer to the ugettext function the use of 
plural forms is prohibited because that requires a different function. 
But perhaps that's O.K. because the validator is only called on scalar 
values.

Anyway, we don't use the ugettext parameter and I don't see the purpose. 
Do you?

Detail: In parameter.py ugettext is called on class variables 
initialized with _(). This is correct. Why? Class variables are 
evaluated at compile time, the _() function will return the msgid 
unmodified. The string must be marked for translation with _(). Thus at 
runtime ugettext needs to be invoked again on the string. But this is a 
consequence of it being evaluated at compile time. None of our 
validation functions use i18n strings which are evaluated at compile 
time, thus there is no need to pass ugettext to them. If for some reason 
they did, they should call the proper version o fugettext from text.py, 
thus I still don't see the need or value of always passing ugettext as a 
parameter to the validator, do you?


-- 
John Dennis <jdennis at redhat.com>

Looking to carve out IT costs?
www.redhat.com/carveoutcosts/




More information about the Freeipa-devel mailing list