On 16 Dec 2005 16:55:52 -0800, "[EMAIL PROTECTED]" <[EMAIL PROTECTED]> wrote: [...]
>When I first wrote this thing it was 3 stage construction, not 2. I >wanted to abstract out specific tests and then JUST apply fields to >them in the actual rules for the business code. I then figured out if I >standardize having the fields as the last arguments on the check >routines, then I could use a simple curry technique to build the >abstraction. > >from functional import curry I'm not familiar with that module, but I wrote a byte-code-munging curry as a decorator that actually modified the decorated function to eliminate parameter(s) from the signature and preset the parameter values inside the code. I mention this because pure-python currying that I've seen typically creates a wrapper function that calls the original function, and slows things down with the extra calling instead of speeding things up, at least until some future version of python. Looks nice on the surface though. > >comp_rule = curry(check)(do_complicated_test) > >Then in the actual business code, can use it like this: > >comp_rule(('a', 'b')) > >Or: > >ssn_rule = curry(match)(is_ssn) > >ssn_rule('my-social-security-field') > >For this to work properly and easily for the business logic. I do need >to get down some more solid rule invocation standards. > > >>>(have('length'), have('width')), >>> check(['length', 'width'], lambda x, y: x == y)) >>>assert rule({'length' : '2', 'width' : '2'}) == True >>>assert rule({'length' : '2', 'width' : '1'}) == False >>>assert rule({'length' : '1', 'width' : '2'}) == False >> >>But what about when the "when" clause says the rule does not apply? >>Maybe return NotImplemented, (which passes as True in an if test) e.g., > >I don't really see why a special exception needs to be here because >"when" easily falls into the logic. When is basically the truth >function, "if A therefore B" and when(A, B) => NOT A OR B > I was suggesting a distinction between testing and production failure detection. The latter of course can treat not-applicable as logically the same as applicable-and-did-not-fail. Note that NotImplemented is not the same as NotImplementedError -- and I wasn't suggesting raising an exception, just returning a distinguishable "True" value, so that a test suite (which I think you said the above was from) can test that the "when" guard logic is working vs just passing back a True which can't distinguish whether the overall test passed or was inapplicable. Of course, to assert a did-not-fail condition, bool(NotImplemented) tests as True, so the assertion (if that's what you want to do) would be written assert rule(...), 'You only see this if rule was applicable and did not pass' # since assert NotImplented succeeds and assert True succeeds # w/o raising AssertionError and False means rule was applicable and failed. for testing purposes you can distinguish results: assert rule(...) is True, 'You only see this if rule was not applicable or test failed' assert rule(...) is False, 'You only see this if rule was not applicable or test succeeded' assert rule(...) is NotImplemented, 'You see this if rule WAS applicable, and either passed or failed OTOH, I can see returning strictly True or False. It's a matter of defining the semantics. >If A is false, then the expression is always true. > >def when(predicate, expr): > """ when(rule predicate, rule expr) -> rule > """ > def rule(record): > if predicate(record): > return expr(record) > else: > return True > return rule > >So if the "test" fails, then the expression is True OR X, which is >always True. Sure. > >Or, form another POV, the entire system is based on conjunction. >Returning "true" means that it doesn't force the entire validation >False. When the predicate fails on the When expression, then the >"therefore" expression does not matter because the predicate failed. >Therefore, the expresison is true no matter what. > >Trying not to be condesending, I just think that the typical >propositional logic works well in this case. >Because this fits well into the system, can you give me an example why >this should make an exception to the boolean logic and raise an >exception? First, I didn't suggest raising an exception, I suggested returning an alternate value (NotImplemented) that python also considers logically true in an "assert value" or "if value" context, so it could be distinguised, if desired, in a test suite (as I believe you said your snippets were from). Of course if you want to write "assert expr == True" as opposed to just "assert expr", you are writing a narrower assertion. Note: >>> assert NotImplemented, 'assertion failed' >>> assert True, 'assertion failed' >>> assert False, 'assertion failed' Traceback (most recent call last): File "<stdin>", line 1, in ? AssertionError: assertion failed If your rule was returning NotImplemented as a "true" value, you wouldn't use the narrowed assertion in a production context, you'd just use the normal bare python-truth assertion. I.e., (using the dummy rule to illustrate) >>> def rulereturning(x): return x ... you would not write >>> assert rulereturning(NotImplemented)==True, 'assertion failed' Traceback (most recent call last): File "<stdin>", line 1, in ? AssertionError: assertion failed You would write >>> assert rulereturning(NotImplemented), 'assertion failed' (That passed) BTW, note that unless you use 'is', expr==True is not as narrow as you might think: >>> assert 1.0==True, 'assertion failed' or >>> assert rulereturning(1.0)==True, 'assertion failed' (both passed sans exception) >The inline code generation is an interesting concept. It would >definately run faster. Hmm, I did this little test. > [...] BTW, is this data coming from an actual DBMS system? If so, wouldn't there be native facilities to do all this validation? Or is Python just too much more fun than SQL? ;-) [...] > >The thing I'm wrestling with now is the identity of rules and fields. >I'd like to fully support localization. Making a localization library >that will provide string tables for "error messages". Could use >positions to do this. IWT dict keys would be more robust. Maybe just use the english string as the key for looking up alternates? BTW strings could also name data to be formatted, as in '%(this)s and %(that_num)d' % mappingobject, where mappingobject can be anything that looks up values per mappingobject.__getitem__(name) where name would be 'this' or 'that' here. > >rules = [ > have('first_name'), > have('last_name'), > all(have('ssn'), match(is_ssn, 'ssn')), > when(all(have('birth_date'), have('hire_date')), lt('birth_date', >'hire_date')) >] > >msgs = [ > locale('MISSING_VALUE', 'FIRST_NAME'), > locale('MISSING_VALUE', 'LAST_NAME'), > locale('INVALID_SSN'), > locale('INVALID_BIRTH_HIRE_DATE'), >] Not to distract you, but I'm wondering what your rules would look like represented in simple rule-per-line text, if you assume that all names are data base field names unless otherwise declared, and just e.g. "name!" would be short for "have(name)", then just use some format like (assuming 'rules' is a name for a particular group of rules) functions:: "is_ssn" # predeclare user-defined function names operators:: "and", "<" # might be implicit rules: first_name!; last_name!; ssn! and is_ssn(ssn); birth_date! and hire_date! and (birth_date < hire_date) or with identation, and messages after a separating comma a la assert rules: first_name!, 'Missing first name' # or ('MISSING_VALUE', 'FIRST_NAME') tuple as args for your locale thing last_name!, 'Missing last name' ssn! and is_ssn(ssn), 'Invlaid ssn' # => 'ssn' in record and is_ssn(record['ssn'] birth_date! and hire_date! and (birth_date < hire_date) # => 'birtdate' in record and 'hire_date' in record and # record['birth_date'] < record['hire_date'] more_rules: field_name!, 'field name is missing' etc ... Seem like it would be fairly easy to translate to code in a function per rule group, without any fancy parsing. I don't like XML that much, but that might be a possible representation too. Regards, Bengt Richter -- http://mail.python.org/mailman/listinfo/python-list