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

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

# Copyright (c) 2014, Facebook, Inc.  All rights reserved. 

# 

# This source code is licensed under the BSD-style license found in the 

# LICENSE file in the root directory of this source tree. An additional grant 

# of patent rights can be found in the PATENTS file in the same directory. 

# 

"""Module for common base classes and helpers, such as options and counters""" 

from collections import namedtuple 

from functools import partial 

from six import iteritems 

 

 

class _Nameable(object): 

    """Base class for attribute classes with automatically set `name` attribute""" 

    def __init__(self, name): 

        super(_Nameable, self).__init__() 

        self.name = name 

 

    def _getNameForIdentifier(self, name): 

        return name 

 

 

class _Bindable(object): 

    """Helper class for allowing instance-unique class-declarative behavior.""" 

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

        self._bound = {} 

        super(_Bindable, self).__init__(*args, **kwargs) 

 

    def __get__(self, instance, owner): 

31        if owner is None: 

            return self 

 

        if owner not in self._bound: 

            self._bound[owner] = self._bind(owner) 

 

        return self._bound[owner] 

 

    def _bind(self, obj): 

        raise NotImplementedError() 

 

class ProvidesCounters(object): 

    def _genCounterCallbacks(self): 

        """Yields this item's (names, value) counter tuple(s).""" 

        raise NotImplementedError() 

 

_AddArgArgs = namedtuple('_AddArgArgs', ['opts', 'kwargs']) 

 

class option(_Nameable): 

    def __init__(self, name=None, type=None, default=None, help=None, 

                 action=None, metavar=None, required=False, choices=None, 

                 nargs=None): 

        super(option, self).__init__(name) 

 

        # Set defaults for action=storeX to bool (otherwise, str) 

        if type is None: 

            if action in ['store_true', 'store_false']: 

                type = bool 

            else: 

                type = str 

 

        self.type = type 

        self.default = default 

        self.help = help 

        self.action = action 

        self.metavar = metavar 

        self.required = required 

        self.choices = choices 

        self.nargs = nargs 

 

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

        if obj is None: 

            return self 

 

        value = self._getter(obj)(self.name) 

 

        # If the default is of a different type than the option requires, 

        # we should return the default.  Unfortunately, the way this is 

        # currently implemented, it's impossible to detect this case.  For now, 

        # let's treat `None` like a special case and return it as-is. 

81        if value is None: 

            return value 

 

85        if self.type is not None: 

            value = self.type(value) 

        return value 

 

    def __set__(self, obj, value): 

90        if self.type is not None: 

            value = self.type(value) 

        self._setter(obj)(self.name, value) 

 

    def _getter(self, obj): 

        getter = getattr(obj, 'getTaskOption', None) 

95        if getter is None: 

            getter = getattr(obj, 'getOption', None) 

        assert getter is not None 

        return getter 

 

    def _setter(self, obj): 

        setter = getattr(obj, 'setTaskOption', None) 

102        if setter is None: 

            setter = getattr(obj, 'setOption', None) 

        assert setter is not None 

        return setter 

 

    def _prepareForArgumentParser(self, task_cls): 

        """Convert inst properties to *args, **kwargs for ap.add_argument(). 

        """ 

        name = task_cls._loptName(self.name) 

 

        # This is kinda funky.  I want to support some form of shorthand 

        # notation for overridable default values.  Doing it this way means we 

        # can do option(..., default=lambda cls: cls.FOO, ...) 

        default = self.default 

        if callable(default): 

            default = default(task_cls) 

 

        kwargs = dict(default=default, help=self.help, action=self.action, 

                      required=self.required) 

        if self.action is None: 

            kwargs['metavar'] = self.metavar 

            kwargs['type'] = self.type 

            kwargs['choices'] = self.choices 

        if self.nargs is not None: 

            kwargs['nargs'] = self.nargs 

        return _AddArgArgs([name], kwargs) 

 

    def _addToArgumentParser(self, optargs, ap): 

        ap.add_argument(*optargs.opts, **optargs.kwargs) 

 

    def _getNameForIdentifier(self, name): 

        return name.replace('_', '-') 

 

 

class _NameHelper(type): 

    def __new__(cls, name, bases, attrs): 

 

        for k, v in iteritems(attrs): 

            # Assign `name` for options 

            if not isinstance(v, _Nameable): 

                continue 

            if v.name is not None: 

                continue 

            v.name = v._getNameForIdentifier(k) 

        return super(_NameHelper, cls).__new__(cls, name, bases, attrs) 

 

 

_SpartsObjectBase = _NameHelper('_SpartsObjectBase', (object, ), {}) 

 

class _SpartsObject(_SpartsObjectBase): 

    def __new__(cls, *args, **kwargs): 

        inst = super(_SpartsObject, cls).__new__(cls) 

        inst.counters = {} 

        #for k, v in iteritems(cls.__dict__): 

 

        # Traverse all child objects and statically assign a callable 

        # reference to all child counters to the instance's counters dictionary. 

        # 

        # This is sort of implicitly broken for Callback counters, which are 

        # defined after __new__ is called (e.g., during Task initialization) 

        # TODO: Implement this in a better way. 

        for k in dir(cls): 

            v = getattr(cls, k) 

            if isinstance(v, ProvidesCounters): 

                for cn, cv in v._genCounterCallbacks(): 

                    inst.counters[cn] = cv 

 

        return inst 

 

    @classmethod 

    def _loptName(cls, name): 

        raise NotImplementedError() 

 

    def getCounters(self): 

        result = {} 

177        for k in self.counters: 

            result[k] = self.getCounter(k) 

 

        for cn, c in iteritems(self.getChildren()): 

            for k in c.counters: 

                result[cn + '.' + k] = c.getCounter(k) 

 

        return result 

 

    def getCounter(self, name): 

        # Hack to get counters from a child task, even if the callable 

        # wasn't statically assigned to this instance's counters dictionary. 

        # TODO: Figure out a better way to do this. 

        if name not in self.counters and '.' in name: 

            child, sep, name = name.partition('.') 

            return self.getChild(child).getCounter(name) 

 

exit        return self.counters.get(name, lambda: None) 

 

    def getChild(self, name): 

        return self.getChildren()[name] 

 

    def getChildren(self): 

        return {} 

 

    @classmethod 

    def _addArguments(cls, ap): 

        options = get_options(cls) 

        for opt in options: 

            opt.regfunc(ap) 

 

 

_OptRegFunc = namedtuple('_OptRegFunc', ['opt', 'regfunc']) 

 

def get_options(cls): 

    """Get argparse options for class, `cls` 

 

    Look for class level attributes of type option and 

    convert them into the arguments  necessary for calling 

    parser.add_argument(). 

 

    Arguments: 

        subclass of VTask or Vservice - `cls` 

 

    Returns: 

        list(namedtuple) - 

            .opt - namedtuple 

                   .args - list of argument string names 

                          (e.g. ['--help', '-h']) 

                   .kwargs - dict of kwargs for add_argument() 

                          (e.g. default=False, action='store_true' etc) 

            .regfunc - callable that takes the ArgumentParser as an argument 

                       and adds the option to it. 

                       (e.g. "foo.regfunc(ap)" registers the foo option on ap) 

    """ 

    ret = [] 

    for k in dir(cls): 

        v = getattr(cls, k) 

        preparefunc = getattr(v, '_prepareForArgumentParser', None) 

        if not preparefunc: 

            continue 

        opt = preparefunc(cls) 

        regfunc = partial(getattr(v, '_addToArgumentParser'), opt) 

        ret.append(_OptRegFunc(opt, regfunc)) 

    return ret