1
"""The PEAK Rules Framework"""
3
from peak.rules.core import abstract, when, before, after, around, istype, \
4
DispatchError, AmbiguousMethods, NoApplicableMethods, value
6
def combine_using(*wrappers):
7
"""Designate a generic function that wraps the iteration of its methods
9
Standard "when" methods will be combined by iteration in precedence order,
10
and the resulting iterator will be passed to the supplied wrapper(s), last
11
first. (e.g. ``combine_using(sorted, itertools.chain)`` will chain the
12
sequences supplied by each method into one giant list, and then sort it).
14
As a special case, if you include ``abstract`` in the wrapper list, it
15
will be removed, and the decorated function will be marked as abstract.
17
This decorator can only be used once per function, and can't be used if
18
the generic function already has methods (even the default method) or if
19
a custom method type has already been set (e.g. if you already called
20
``combine_using()`` on it before).
22
is_abstract = abstract in wrappers
24
wrappers = tuple([w for w in wrappers if w is not abstract])
26
def callback(frame, name, func, old_locals):
27
if core.Dispatching.exists_for(func) and list(core.rules_for(func)):
28
raise RuntimeError("Methods already defined for", func)
31
r = core.Dispatching(func).rules
32
if r.default_actiontype is not core.Method:
33
raise RuntimeError("Method type already defined for", func)
34
r.default_actiontype = core.MethodList.make
35
r.methodlist_wrappers = wrappers[::-1]
37
r.add(core.Rule(core.clone_function(func)))
39
return core.decorate_assignment(callback)
42
def expand_as(predicate_string):
43
"""In rules, use the supplied condition in place of the decorated function
46
@expand_as('filter is None or value==filter')
47
def check(filter, value):
48
"Check whether value matches filter"
50
When the above function is used in a rule, the supplied condition will
51
be "inlined", so that PEAK-Rules can expand the function call without
52
losing its ability to index the rule or determine its precedence relative
55
When the condition is inlined, it's done in a namespace-safe manner.
56
Names in the supplied condition will refer to either the arguments of the
57
decorated function, or to locals/globals in the frame where ``expand_as``
58
was called. Names defined in the condition body (e.g. via ``let``) will
59
not be visible to the caller.
61
To prevent needless code duplication, you do not need to provide a body for
62
your function, unless it is to behave differently than the supplied
63
condition when it's called outside a rule. If the decorated function has
64
no body of its own (i.e. it's a ``pass`` or just a docstring), the supplied
65
condition will be compiled to provide one automatically. (That's why the
66
above example usage has no body for the ``check()`` function.)
68
def callback(frame, name, func, old_locals):
69
from peak.rules.predicates import _expand_as
70
kind, module, locals_, globals_ = core.frameinfo(frame)
72
func, predicate_string, locals_, globals_, __builtins__
74
return core.decorate_assignment(callback)
84
"""Define temporary variables for use in rules and methods
88
@when(somefunc, "let(x=foo(y), z=y*2) and x>z")
89
def some_method((x,z), next_method, y):
92
The keywords used in the let() expression become available for use in
93
any part of the rule that is joined to the ``let()`` by an ``and``
94
expression, but will not be available in expressions joined by ``or`` or
95
``not`` branches. Any ``let()`` calls at the top level of the expression
96
will also be available for use in the method body, if you place them in
97
a tuple argument in the *very first* argument position -- even before
98
``next_method`` and ``self``.
100
Note that variables defined by ``let()`` are **lazy** - their values are
101
not computed until/unless they are actually needed by the relevant part
102
of the rule, so it does not slow things down at runtime to list all your
103
variables up front. Likewise, only the variables actually listed in your
104
first-argument tuple are calculated, and only when the method is actually
107
(Currently, this feature is mainly to support easy-to-understand rules,
108
and DRY method bodies, as variables used in the rule's criteria may be
109
calculated a second time when the method is invoked.)
111
Note that while variable calculation is lazy, there *is* an evaluation
112
order *between* variables in a let; you can't use a let-variable before
113
it's been defined; you'll instead get whatever argument, local, or global
114
variable would be shadowed by the as-yet-undefined variable.
116
raise NotImplementedError("`let` can only be used in rules, not code!")
118
__all__ = [_k for _k in list(globals()) if not _k.startswith('_') and _k!='core']
119
# TEMPORARY BACKWARDS COMPATIBILITY - PLEASE IMPORT THIS DIRECTLY FROM CORE
120
# (or better still, use the '>>' operator that method types now have)
122
from peak.rules.core import * # always_overrides, Method, etc.