~divmod-dev/divmod.org/trunk

« back to all changes in this revision

Viewing changes to Epsilon/epsilon/expose.py

  • Committer: Jean-Paul Calderone
  • Date: 2014-06-29 20:33:04 UTC
  • mfrom: (2749.1.1 remove-epsilon-1325289)
  • Revision ID: exarkun@twistedmatrix.com-20140629203304-gdkmbwl1suei4m97
mergeĀ lp:~exarkun/divmod.org/remove-epsilon-1325289

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copright 2008 Divmod, Inc.  See LICENSE file for details.
2
 
# -*- test-case-name: epsilon.test.test_expose -*-
3
 
 
4
 
"""
5
 
This module provides L{Exposer}, a utility for creating decorators that expose
6
 
methods on types for a particular purpose.
7
 
 
8
 
The typical usage of this module is for an infrastructure layer (usually one
9
 
that allows methods to be invoked from the network, directly or indirectly) to
10
 
provide an explicit API for exposing those methods securely.
11
 
 
12
 
For example, a sketch of a finger protocol implementation which could use this
13
 
to expose the results of certain methods as finger results::
14
 
 
15
 
    # tx_finger.py
16
 
    fingermethod = Exposer("This object exposes finger methods.")
17
 
    ...
18
 
    class FingerProtocol(Protocol):
19
 
        def __init__(self, fingerModel):
20
 
            self.model = fingerModel
21
 
        ...
22
 
        def fingerQuestionReceived(self, whichUser):
23
 
            try:
24
 
                method = fingermethod.get(self.model, whichUser)
25
 
            except MethodNotExposed:
26
 
                method = lambda : "Unknown user"
27
 
            return method()
28
 
 
29
 
    # myfingerserver.py
30
 
    from tx_finger import fingermethod
31
 
    ...
32
 
    class MyFingerModel(object):
33
 
        @fingermethod.expose("bob")
34
 
        def someMethod(self):
35
 
            return "Bob is great."
36
 
 
37
 
Assuming lots of protocol code to hook everything together, this would then
38
 
allow you to use MyFingerModel and 'finger bob' to get the message 'Bob is
39
 
great.'
40
 
"""
41
 
 
42
 
import inspect
43
 
 
44
 
from types import FunctionType
45
 
 
46
 
 
47
 
class MethodNotExposed(Exception):
48
 
    """
49
 
    The requested method was not exposed for the purpose requested.  More
50
 
    specifically, L{Exposer.get} was used to retrieve a key from an object
51
 
    which does not expose that key with that exposer.
52
 
    """
53
 
 
54
 
 
55
 
class NameRequired(Exception):
56
 
    """
57
 
    L{Exposer.expose} was used to decorate a non-function object without having
58
 
    a key explicitly specified.
59
 
    """
60
 
 
61
 
 
62
 
 
63
 
class Exposer(object):
64
 
    """
65
 
    This is an object that can expose and retrieve methods on classes.
66
 
 
67
 
    @ivar _exposed: a dict mapping exposed keys to exposed function objects.
68
 
    """
69
 
 
70
 
    def __init__(self, doc):
71
 
        """
72
 
        Create an exposer.
73
 
        """
74
 
        self.__doc__ = doc
75
 
        self._exposed = {}
76
 
 
77
 
 
78
 
    def expose(self, key=None):
79
 
        """
80
 
        Expose the decorated method for this L{Exposer} with the given key.  A
81
 
        method which is exposed will be able to be retrieved by this
82
 
        L{Exposer}'s C{get} method with that key.  If no key is provided, the
83
 
        key is the method name of the exposed method.
84
 
 
85
 
        Use like so::
86
 
 
87
 
            class MyClass:
88
 
                @someExposer.expose()
89
 
                def foo(): ...
90
 
 
91
 
        or::
92
 
 
93
 
            class MyClass:
94
 
                @someExposer.expose('foo')
95
 
                def unrelatedMethodName(): ...
96
 
 
97
 
        @param key: a hashable object, used by L{Exposer.get} to look up the
98
 
        decorated method later.  If None, the key is the exposed method's name.
99
 
 
100
 
        @return: a 1-argument callable which records its input as exposed, then
101
 
        returns it.
102
 
        """
103
 
        def decorator(function):
104
 
            rkey = key
105
 
            if rkey is None:
106
 
                if isinstance(function, FunctionType):
107
 
                    rkey = function.__name__
108
 
                else:
109
 
                    raise NameRequired()
110
 
            if rkey not in self._exposed:
111
 
                self._exposed[rkey] = []
112
 
            self._exposed[rkey].append(function)
113
 
            return function
114
 
        return decorator
115
 
 
116
 
 
117
 
    def get(self, obj, key):
118
 
        """
119
 
        Retrieve 'key' from an instance of a class which previously exposed it.
120
 
 
121
 
        @param key: a hashable object, previously passed to L{Exposer.expose}.
122
 
 
123
 
        @return: the object which was exposed with the given name on obj's key.
124
 
 
125
 
        @raise MethodNotExposed: when the key in question was not exposed with
126
 
        this exposer.
127
 
        """
128
 
        if key not in self._exposed:
129
 
            raise MethodNotExposed()
130
 
        rightFuncs = self._exposed[key]
131
 
        T = obj.__class__
132
 
        seen = {}
133
 
        for subT in inspect.getmro(T):
134
 
            for name, value in subT.__dict__.items():
135
 
                for rightFunc in rightFuncs:
136
 
                    if value is rightFunc:
137
 
                        if name in seen:
138
 
                            raise MethodNotExposed()
139
 
                        return value.__get__(obj, T)
140
 
                seen[name] = True
141
 
        raise MethodNotExposed()