~ubuntu-branches/ubuntu/quantal/python-peak.rules/quantal

« back to all changes in this revision

Viewing changes to rules/peak/rules/__init__.py

  • Committer: Package Import Robot
  • Author(s): Barry Warsaw
  • Date: 2011-10-21 18:51:25 UTC
  • mfrom: (0.8.1) (0.7.1) (1.4.1) (4.1.2 precise)
  • Revision ID: package-import@ubuntu.com-20111021185125-tasydfgcvgc6ynyh
Tags: 0.5a1+r2707-1fakesync1
Fake sync due to mismatching orig tarball.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""The PEAK Rules Framework"""
 
2
 
 
3
from peak.rules.core import abstract, when, before, after, around, istype, \
 
4
    DispatchError, AmbiguousMethods, NoApplicableMethods, value
 
5
 
 
6
def combine_using(*wrappers):
 
7
    """Designate a generic function that wraps the iteration of its methods
 
8
 
 
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).
 
13
 
 
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.
 
16
 
 
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).
 
21
    """
 
22
    is_abstract = abstract in wrappers
 
23
    if is_abstract:
 
24
        wrappers = tuple([w for w in wrappers if w is not abstract])
 
25
        
 
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)
 
29
        if is_abstract:
 
30
            func = abstract(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]
 
36
        if not is_abstract:
 
37
            r.add(core.Rule(core.clone_function(func)))
 
38
        return func
 
39
    return core.decorate_assignment(callback)
 
40
 
 
41
 
 
42
def expand_as(predicate_string):
 
43
    """In rules, use the supplied condition in place of the decorated function
 
44
 
 
45
    Usage::
 
46
        @expand_as('filter is None or value==filter')
 
47
        def check(filter, value):
 
48
            "Check whether value matches filter"
 
49
 
 
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
 
53
    to other rules.
 
54
 
 
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.
 
60
 
 
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.)
 
67
    """    
 
68
    def callback(frame, name, func, old_locals):
 
69
        from peak.rules.predicates import _expand_as
 
70
        kind, module, locals_, globals_ = core.frameinfo(frame)
 
71
        return _expand_as(
 
72
            func, predicate_string, locals_, globals_, __builtins__
 
73
        )
 
74
    return core.decorate_assignment(callback)
 
75
 
 
76
 
 
77
 
 
78
 
 
79
 
 
80
 
 
81
 
 
82
 
 
83
def let(**kw):
 
84
    """Define temporary variables for use in rules and methods
 
85
 
 
86
    Usage::
 
87
 
 
88
        @when(somefunc, "let(x=foo(y), z=y*2) and x>z")
 
89
        def some_method((x,z), next_method, y):
 
90
            # do something here
 
91
 
 
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``.
 
99
 
 
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
 
105
    invoked.
 
106
 
 
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.)
 
110
 
 
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.
 
115
    """
 
116
    raise NotImplementedError("`let` can only be used in rules, not code!")
 
117
 
 
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)
 
121
#
 
122
from peak.rules.core import *  # always_overrides, Method, etc.
 
123