Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

from twisted.internet import defer 

 

 

class Receiver(object): 

    def __init__(self, signal, callback, args, kwargs): 

        self.signal = signal 

        self.callback = callback 

        self.arguments = args 

        self.keywords = kwargs 

 

    def __call__(self, *args, **kwargs): 

        args = args + self.arguments 

 

        kw = self.keywords.copy() 

        if kwargs: 

            kw.update(kwargs) 

        return self.callback(*args, **kw) 

 

    def __repr__(self): 

        return \ 

            "<Receiver %s for %s: %s (%s, %s)>" % \ 

            (id(self), self.signal, self.callback, 

             ', '.join( 

                 ['%r' % x for x in self.arguments]), 

             ', '.join( 

                 ['%s=%s' % (x, y) for x, y in self.keywords.items()]) 

             ) 

 

 

class UnknownSignal(Exception): 

    pass 

 

 

class Dispatcher(object): 

    __signals__ = {} 

 

    def __init__(self): 

        self.receivers = {} 

        for signal in self.__signals__.keys(): 

            self.receivers[signal] = [] 

 

    def connect(self, signal, callback, *args, **kw): 

        receiver = Receiver(signal, callback, args, kw) 

        try: 

            self.receivers[signal].append(receiver) 

        except KeyError: 

            raise UnknownSignal(signal) 

        return receiver 

 

    def disconnect(self, receiver): 

        if not receiver: 

            return 

 

        try: 

            self.receivers[receiver.signal].remove(receiver) 

        except KeyError: 

            raise UnknownSignal(receiver.signal) 

        except AttributeError: 

            raise TypeError("'%r' is not a Receiver-like object" % receiver) 

        except ValueError: 

            # receiver not in the list, goal achieved 

            pass 

 

    def emit(self, signal, *args, **kwargs): 

        results = [] 

        errors = [] 

        for receiver in self._get_receivers(signal): 

            try: 

                results.append((receiver, receiver(*args, **kwargs))) 

            except Exception as e: 

                errors.append((receiver, e)) 

 

        return results, errors 

 

    def deferred_emit(self, signal, *args, **kwargs): 

        receivers = [] 

        dfrs = [] 

        # TODO: the loop is blocking, use callLaters and/or coiterate here 

        for receiver in self._get_receivers(signal): 

            receivers.append(receiver) 

            dfrs.append(defer.maybeDeferred(receiver, *args, **kwargs)) 

 

        if not dfrs: 

            return defer.succeed([]) 

 

        result_dfr = defer.DeferredList(dfrs) 

        result_dfr.addCallback(self._merge_results_and_receivers, receivers) 

        return result_dfr 

 

    def save_emit(self, signal, *args, **kwargs): 

        deferred = defer.Deferred() 

        # run the deferred_emit in as a callback 

        deferred.addCallback(self.deferred_emit, *args, **kwargs) 

        # and callback the deferred with the signal as the 'result' in the 

        # next mainloop iteration 

        from twisted.internet import reactor 

        reactor.callLater(0, deferred.callback, signal) 

        return deferred 

 

    def _merge_results_and_receivers(self, result, receivers): 

        # make a list of (rec1, res1), (rec2, res2), (rec3, res3) ... 

        return [(receiver, result[counter]) 

                for counter, receiver in enumerate(receivers)] 

 

    def _get_receivers(self, signal): 

        try: 

            return self.receivers[signal] 

        except KeyError: 

            raise UnknownSignal(signal) 

 

 

class SignalingProperty(object): 

    """ 

    Does emit self.signal when the value has changed but only if HAS changed 

    (means old_value != new_value). 

    """ 

 

    def __init__(self, signal, var_name=None, default=None): 

        self.signal = signal 

 

        if var_name is None: 

            var_name = "__%s__val" % signal 

 

        self.var_name = var_name 

 

        self.default = default 

 

    def __get__(self, obj, objtype=None): 

        return getattr(obj, self.var_name, self.default) 

 

    def __set__(self, obj, value): 

        if self.__get__(obj) == value: 

            return 

        setattr(obj, self.var_name, value) 

        obj.emit(self.signal, value) 

 

 

class ChangedSignalingProperty(SignalingProperty): 

    """ 

    Does send the signal with two values when changed: 

        1. the new value 

        2. the value it has been before 

    """ 

 

    def __set__(self, obj, value): 

        before = self.__get__(obj) 

        if before == value: 

            return 

        setattr(obj, self.var_name, value) 

        obj.emit(self.signal, value, before) 

 

 

class CustomSignalingProperty(object): 

    """ 

    Signal changes to this property. allows to specify fget and fset as the 

    build in property-decorator. 

    """ 

 

    def __init__(self, signal, fget, fset, fdel=None, doc=None): 

        """ 

        fdel is there for API compability only. As there is no good way to 

        signal a deletion it is not implemented at all. 

        """ 

        self.signal = signal 

        self.fget = fget 

        self.fset = fset 

        self.__doc__ = doc 

 

    def __get__(self, obj, objtype): 

        return self.fget(obj) 

 

    def __set__(self, obj, value): 

        """ 

        Call fset with value. Call fget before and after to figure out if 

        something actually changed. Only if something changed the signal is 

        emitted. 

 

        The signal will be emitted with the new value given by fget. 

 

        *Note*: This means that fset might gets called with the same value 

        twice while the signal is not emitted a second time. You might want 

         to check for that in your fset. 

        """ 

 

        old_value = self.fget(obj) 

        self.fset(obj, value) 

        new_value = self.fget(obj) 

 

        if old_value == new_value: 

            return 

 

        obj.emit(self.signal, new_value)