Tuesday, August 10, 2010

Simplifying prioritized methods in PEAK-Rules

Recently, I've been scouting around the web for examples of what people have been doing with PEAK-Rules (and the older RuleDispatch package) to get an idea of what else I should put in (if anything) before making the first official release.

One of the interesting things I found was a package called prioritized_methods, which advertises itself as a "prioritized" version of PEAK-Rules, and appears to have been used in the ToscaWidgets project at one point.

Prioritized methods certainly seem like a useful idea, but I was a bit bothered by the specific implementation, because it showed just how weak PEAK-Rules' extensibility documentation is at this point.

Really, it shouldn't be that hard to implement manual method priorities in PEAK-Rules.  I mean, prioritized_methods is like 150 lines plus docstrings, it has to define several new method types and decorators to replace those in PEAK-Rules, and if you want to use it with a new method type of your own, you're already screwed by potential incompatibilities.

In short, I clearly wasn't exposing a good enough API or providing good enough examples.  ;-)

So, there had to be a better way, and in fact I immediately thought of one that ought to be doable in a dozen lines of code or so, that would make a perfect demo for PEAK-Rules' predicates module documentation:

from peak.rules import implies, when
from peak.rules.criteria import Test
from peak.rules.predicates import Const, expressionSignature

class priority(int):
    """A simple priority"""

when(implies, (priority, priority))(lambda p1,p2: p1>p2)

      "isinstance(expr, Const) and "
      "isinstance(expr.value, priority)")
def test_for_priority(expr):
    return Test(None, expr.value)

What this code does is create an integer subclass called priority, that can then be used in rule definitions, e.g.:

@when(some_func, "isinstance(foo, Bar) and priority(1)")

Then, between two otherwise-identical rules, the one with a priority will win over the one without, or the higher priority will win if both have priorities.

All you have to do to use it, is import the priority type into the modules where you want to use it. No new decorators or special method types are needed, and it will continue to work with any new method types added to PEAK-Rules or defined by third parties!

Pretty neat, huh?

There was just one downside to it, and that's that it didn't work. :-(

As it happens, PEAK-Rules' predicate dispatch engine barfed on using None as a test expression (in the Test(None, expr.value) part), and I had to tweak a few lines to make it skip over indexing and code generation for tests on None. But, once that was done, I was able to add a tested version of the above as a doctest demo.

Anyway, if you're doing anything interesting with PEAK-Rules, or find yourself needing to extend it in some way, I'd love to hear from you. Right now, it's pretty easy for me to add cool features like the one above, but I'm guessing that there are still some gaps in the current documentation for anybody else trying to implement nifty new features like the above.

So, I'm especially interested in any problems you had doing extensions, as well as success stories. (I'd really like to start firming up the extension APIs soon, as well as their docs!)