durusmail: qp: ConnectiveSpecOperator: how to know which spec gave out?
ConnectiveSpecOperator: how to know which spec gave out?
2006-02-26
2006-02-27
2006-02-27
2006-02-27
2006-02-27
2006-02-27
2006-02-28
2006-02-28
ConnectiveSpecOperator: how to know which spec gave out?
mario ruggier
2006-02-27
On Feb 27, 2006, at 3:11 PM, David Binger wrote:
> On Feb 27, 2006, at 2:39 AM, mario ruggier wrote:
>
>> I of course meant something like this
>
> I may have lost track of what it is that your suggested
> change fixes.
>
> If I remember correctly, this is just about wanting more
> detailed error explanations when an "both" spec is not
> matched.

Eh, no. I do not really care about detailed error explanations...
except while developing of course. What I care about is the possibility
to associate a spec to something, such as a form field, and to that
spec attach (optionally) 2 messages, one for OK and one for Error. And
those messages are not at all concerned with underlying exceptions, but
are human-application interface messages, to tell for example a user in
plain english, or in french, or german, that a space character in her
userid is verboten. Those messages cannot be coming from within spec
classes, but must be necessarily part of the application-specific
logic. So, just for the sake of a concrete example:

         form.add_password('passwd1', title="Password Confirm",
             required=False,
             hint="Case sensitive. At least 4 chars long. At least 1
digit.",
             spec=spec(both(
                         spec(equalwidget(form, 'passwd2'), 'not
equal.'),
                         spec(pattern('^\d|^\D+\d'), 'at least 1
digit.'),
                         spec(length(4, None), 'too short.')),
                     'Password is not OK... ', 'Password is OK.'))

That will give back the following errors according to which spec failed:
        Password is not OK... not equal.
        Password is not OK... at least 1 digit.
        Password is not OK... too short.
Or, on success:
        Password is OK.

And, this it does both client side prior to submit, as well as
server-side... more on that some other time.

Anyhow, I thought I could either go and subclass every spec class that
I use to allow such messages per spec instance, or wrap something
around the existing spec classes. I opted for this second option (also
for the reason that not all specs are instances of SpecOperator) and
thus I have a wrapper class (used in the example above) whose init
looks like this:

class SpecWithMessages (qp.lib.spec.Specification):
     def __init__(self, spec, error_message=None, ok_message=None,
doc='')
        ...

The client code using specs wrapped in this this way can then set its
own err and ok messages as it pleases and in the language of the user.
And, all it needs to know to be able to get to those messages is which
spec instance failed or succeeded. In the case of normal single
SpecOperator, this is anyway always known, but in the case of
ConnectiveSpecOperators I cannot currently know which sub-spec caused
the failure or the success of the spec.

The Spec.explain_difference(value) pattern you suggest below is for
sure useful for error details from a developer's point of view, but
does feel right as the solution for this, even if I could use/coerce it
for it, thus for "both" the method i would want would simply become:

     def explain_difference(self, value):
         for spec in self.specs:
             if not match(value, spec):
                 return spec

which seems pretty silly to me to go thru that (again) to just get
which spec had just previously failed. Besides, it would anyway still
require to extend the class, as the intended use of
explain_difference() is not this. .

Anyway this issue concerns only compound specs, and how to know which
sub-spec gave failure or gave success.

mario



> It isn't clear to me that that is really necessary,
> but if it is, I think it could be dealt with more directly
> instead of imposing change on all of these other classes.
>
> ===================================================================
> --- lib/spec.py (revision 27998)
> +++ lib/spec.py (working copy)
> @@ -52,6 +52,9 @@
>      def __str__(self):
>          return "%s(%s)" % (self.__class__.__name__,
> self.format_args())
> +    def explain_difference(self, value):
> +        return format_expected_got(self, value)
> +
> def match(value, spec):
>      """
>      Return True or False depending on whether or not value matches
> @@ -141,10 +144,16 @@
>          pass
>      return False
> +def format_expected_got(value, spec):
> +    return ('\n  Expected: %s\n'
> +            '  Got: %r\n') % (format_spec(spec), value)
> +
> def require(value, spec, message=None):
>      if not match(value, spec):
> -        error = ('\n  Expected: %s\n'
> -                 '  Got: %r\n') % (format_spec(spec), value)
> +        if isinstance(spec, SpecOperator):
> +            error = spec.explain_difference(value)
> +        else:
> +            error = format_expected_got(spec, value)
>          if message:
>              error = '(%s)%s' % (message, error)
>          raise TypeError(error)
> @@ -272,6 +281,13 @@
>                  return False
>          return True
> +    def explain_difference(self, value):
> +        error = format_expected_got(value, self)
> +        for spec in self.specs:
> +            if not match(value, spec):
> +                error += "  (which does not match %s)" %
> format_spec(spec)
> +                break
> +        return error
>
>
>
>
> _______________________________________________
> QP mailing list
> QP@mems-exchange.org
> http://mail.mems-exchange.org/mailman/listinfo/qp

reply