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# (c) Stefan Countryman, 2019 

2 

3""" 

4Classes and utilities for communicating with external organizations (e.g. 

5downloading inputs and uploading results, sending alerts, etc.). Pretty much 

6everything having to do with data transfer should be inheriting from primitives 

7in this package. 

8""" 

9 

10import re 

11import sys 

12from llama.filehandler import FileHandler 

13from llama.filehandler.mixins import ( 

14 ObservingVetoMixin, 

15 OnlineVetoMixin, 

16) 

17from llama.flags import FlagDict 

18 

19 

20def upload_flag_false(eventdir): 

21 """Return whether a trigger directory has its "UPLOAD" flag set to 

22 "false".""" 

23 return FlagDict(eventdir)['UPLOAD'] == 'false' 

24 

25 

26# pylint: disable=too-many-ancestors, abstract-method 

27class UploadReceipt(FileHandler, OnlineVetoMixin, ObservingVetoMixin): 

28 """ 

29 A log file created as a side effect of uploading some other original 

30 file to some remote server (like gw-astronomy.org or gracedb). The 

31 generate() function should contain instructions on how to upload the 

32 original file and should log helpful information to the UploadReceipt 

33 file. The presence of an UploadReceipt file should be taken as evidence 

34 that an upload has succeeded. 

35 

36 Define the file that is to be uploaded as ``UPLOAD`` when subclassing 

37 ``UploadReceipt``. 

38 """ 

39 

40 class_vetoes = ( 

41 (upload_flag_false, None), 

42 ) 

43 

44 _REQUIRED = ("UPLOAD", "FILENAME_FMT", "CLASSNAME_FMT") 

45 

46 UPLOAD = None 

47 FILENAME_FMT = None 

48 CLASSNAME_FMT = None 

49 

50 @classmethod 

51 def set_class_attributes(cls, subclass): 

52 """See ``FileHandler.set_class_attributes``; this method additionally 

53 sets the ``FILENAME`` and ``DEPENDENCIES`` attributes based on 

54 ``subclass.UPLOAD``.""" 

55 fmt = subclass.FILENAME_FMT 

56 subclass.FILENAME = fmt.format(subclass.UPLOAD.FILENAME) 

57 subclass.DEPENDENCIES = (subclass.UPLOAD,) 

58 super().set_class_attributes(subclass) 

59 return subclass 

60 

61 @classmethod 

62 def upload_this(cls, *args, namespace=None, **kwargs): 

63 """Return a decorator that automatically uploads ``FileHandler`` 

64 classes by implementing this class with that ``FileHandler`` class as 

65 the ``UPLOAD`` attribute. Additional attributes are set by 

66 ``cls.decorator_dict`` using the ``FileHandler`` class decorated by 

67 the returned ``decorator``, ``*args``, and ``**kwargs``. 

68 

69 Parameters 

70 ---------- 

71 *args 

72 Positional arguments to pass to ``cls.decorator_dict`` (after the 

73 ``FileHandler`` class decorated by the returned ``decorator``, 

74 which takes the first positional argument position). 

75 namespace : dict, optional 

76 The namespace to which the new ``UploadReceipt`` created by 

77 ``decorator`` will be added. The default behavior is to define 

78 ``UploadReceipt`` in the global namespace of the calling frame 

79 (so that using the decorator is equivalent to manually defining the 

80 ``UploadReceipt`` class with the decorated ``FileHandler`` as its 

81 upload immediately after defining the ``FileHandler`` to be 

82 decorated). 

83 **kwargs 

84 Keyword arguments to pass to ``cls.decorator_dict``. 

85 

86 Returns 

87 ------- 

88 decorator : func 

89 A decorator that takes a ``FileHandler`` and returns an 

90 implementation of this class with the decorated ``FileHandler`` as 

91 its ``UPLOAD`` file (along with any other implementation attributes 

92 calculated by this class's ``decorator_dict`` function using the new 

93 ``FileHandler`` class, ``*args``, and ``**kwargs`` as input). The 

94 generated classname will be named 

95 ``cls.CLASSNAME_FMT.format(upload)`` (where ``upload`` is the 

96 decorated ``FileHandler``) and will be added as a variable with 

97 that name to ``namespace``. 

98 

99 Raises 

100 ------ 

101 TypeError 

102 If there is only one positional argument provided which is itself a 

103 ``type`` (since this usually indicates that the developer forgot to 

104 **call** this method to get the *actual* decorator and instead 

105 applied this function as if it were a decorator to the 

106 ``FileHandler`` class they intend to decorate). 

107 Exception 

108 ``decorator_dict`` can, in principle, throw other exceptions as well. 

109 See the specific implementation for details. 

110 """ 

111 if namespace is None and len(args) == 1 and isinstance(args[0], type): 

112 raise TypeError("Didn't expect a ``type`` as the only positional " 

113 "argument (with no keyword arguments provided): " 

114 f"{args[0]} did you forget to pass ``globals()`` " 

115 "as the first argument to ``upload_this`` to get " 

116 f"the actual decorator for {args[0]}?") 

117 if namespace is None: 

118 namespace = sys._getframe(1).f_globals 

119 module = namespace['__name__'] 

120 

121 def decorator(upload): 

122 """Create a new ``UploadReceipt`` subclass implementation to upload 

123 ``upload`` (subclass of ``FileHandler``). See 

124 ``UploadReceipt.upload_this`` for information on which 

125 namespace/module the new ``UploadReceipt`` is saved in. 

126 

127 Raises 

128 ------ 

129 TypeError 

130 If ``cls.decorator_dict`` failed to return a dictionary (or 

131 other mutable dict-like object); if ``*args`` and ``**kwargs`` 

132 don't conform to the actual signature of this class's 

133 ``decorator_dict`` implementation; or if ``upload`` is not a 

134 ``FileHandler`` subclass. 

135 AssertionError 

136 If the class name generated from CLASSNAME_FMT is not a valid 

137 variable name or if this ``UploadReceipt.decorator_dict`` 

138 implementation does not accept a positional variable called 

139 ``'upload'`` as its first positional argument. 

140 """ 

141 if not issubclass(upload, FileHandler): 

142 raise TypeError("Expected ``FileHandler`` subclass, got: " 

143 f"{upload}") 

144 classname = cls.CLASSNAME_FMT.format(upload.__name__) 

145 assert re.findall('^[a-zA-Z_][a-zA-Z0-9_]*$', classname) 

146 vnames = cls.decorator_dict.__code__.co_varnames 

147 if len(vnames) < 2 or vnames[:2] != ('cls', 'upload'): 

148 raise AssertionError( 

149 f"This ``UploadReceipt`` subclass ({cls}) has defined" 

150 "``decorator_dict`` with the wrong function signature: " 

151 f"{cls.__name__}.decorator_dict({', '.join(vnames)}) " 

152 "You MUST have (cls, upload) as the names of the first " 

153 "positional parameters since ``decorator_dict`` is expected " 

154 "to be a classmethod taking the decorated ``FileHandler`` " 

155 "class as its first argument (before receiving the *args " 

156 "and **kwargs passed from ``upload_this``. " 

157 "This code style decision is enforced to avoid subtle " 

158 r"implementation bugs; sorry ¯\_(ツ)_/¯" 

159 ) 

160 newclassdict = cls.decorator_dict(upload, *args, **kwargs) 

161 newclassdict['UPLOAD'] = upload 

162 newclassdict['__module__'] = module 

163 newclass = type(classname, (cls,), newclassdict) 

164 cls.set_class_attributes(newclass) 

165 namespace[classname] = newclass 

166 return upload 

167 

168 return decorator 

169 

170 @classmethod 

171 def decorator_dict(cls, upload): # pylint: disable=unused-argument 

172 """See ``UploadReceipt.upload_this``. Takes the decorated 

173 ``FileHandler`` class as its first argument. By default takes no 

174 additional arguments. 

175 

176 Parameters 

177 ---------- 

178 upload 

179 The decorated ``FileHandler`` class that is being registered for 

180 upload. 

181 

182 Returns 

183 ------- 

184 newclassdict : dict 

185 A dictionary that can be passed to ``type`` to specify the 

186 attributes of a new class. 

187 """ 

188 return dict()