21
21
22
22
class Plugin (metaclass = ABCMeta ):
23
23
"""
24
- Abstract Base class for plugins.
25
- For internal use only.
24
+ Abstract Base class for plugins. Provides a framework for defining and running
25
+ plugins within a specified configuration.
26
+
27
+ Attributes:
28
+ config (PythonConfig): Configuration for the plugin.
29
+ kwargs: Additional keyword arguments.
26
30
"""
27
31
28
32
def __init__ (
@@ -40,6 +44,12 @@ def __init__(
40
44
41
45
@property
42
46
def name (self ):
47
+ """
48
+ Get the name of the plugin.
49
+
50
+ Returns:
51
+ str: The name of the plugin.
52
+ """
43
53
return self ._config .name
44
54
45
55
@classmethod
@@ -50,6 +60,12 @@ def python_base_path(cls) -> PosixPath:
50
60
51
61
@classmethod
52
62
def all_subclasses (cls ):
63
+ """
64
+ Retrieve all subclasses of the plugin class.
65
+
66
+ Returns:
67
+ list: Sorted list of plugin subclasses.
68
+ """
53
69
posix_dir = PosixPath (str (cls .python_base_path ).replace ("." , "/" ))
54
70
for plugin in posix_dir .rglob ("*.py" ):
55
71
if plugin .stem == "__init__" :
@@ -65,30 +81,72 @@ def all_subclasses(cls):
65
81
66
82
@cached_property
67
83
def _job (self ) -> "Job" :
84
+ """
85
+ Get the job associated with the plugin.
86
+
87
+ Returns:
88
+ Job: The job instance.
89
+ """
68
90
return Job .objects .get (pk = self .job_id )
69
91
70
92
@property
71
93
def job_id (self ) -> int :
94
+ """
95
+ Get the job ID.
96
+
97
+ Returns:
98
+ int: The job ID.
99
+ """
72
100
return self ._job_id
73
101
74
102
@job_id .setter
75
103
def job_id (self , value ):
104
+ """
105
+ Set the job ID.
106
+
107
+ Args:
108
+ value (int): The job ID.
109
+ """
76
110
self ._job_id = value
77
111
78
112
@cached_property
79
113
def _user (self ):
114
+ """
115
+ Get the user associated with the job.
116
+
117
+ Returns:
118
+ User: The user instance.
119
+ """
80
120
return self ._job .user
81
121
82
122
def __repr__ (self ):
123
+ """
124
+ Get the string representation of the plugin.
125
+
126
+ Returns:
127
+ str: The string representation of the plugin.
128
+ """
83
129
return str (self )
84
130
85
131
def __str__ (self ):
132
+ """
133
+ Get the string representation of the plugin.
134
+
135
+ Returns:
136
+ str: The string representation of the plugin.
137
+ """
86
138
try :
87
139
return f"({ self .__class__ .__name__ } , job: #{ self .job_id } )"
88
140
except AttributeError :
89
141
return f"{ self .__class__ .__name__ } "
90
142
91
143
def config (self , runtime_configuration : typing .Dict ):
144
+ """
145
+ Configure the plugin with runtime parameters.
146
+
147
+ Args:
148
+ runtime_configuration (dict): Runtime configuration parameters.
149
+ """
92
150
self .__parameters = self ._config .read_configured_params (
93
151
self ._user , runtime_configuration
94
152
)
@@ -104,25 +162,33 @@ def config(self, runtime_configuration: typing.Dict):
104
162
105
163
def before_run (self ):
106
164
"""
107
- function called directly before run function.
165
+ Function called directly before the run function.
108
166
"""
109
167
110
168
@abstractmethod
111
169
def run (self ) -> dict :
112
170
"""
113
- Called from *start* fn and wrapped in a try-catch block.
114
- Should be overwritten in child class
115
- :returns report
171
+ Called from *start* function and wrapped in a try-catch block.
172
+ Should be overwritten in child class.
173
+
174
+ Returns:
175
+ dict: Report generated by the plugin.
116
176
"""
117
177
118
178
def after_run (self ):
119
179
"""
120
- function called after run function.
180
+ Function called after the run function.
121
181
"""
122
182
self .report .end_time = timezone .now ()
123
183
self .report .save ()
124
184
125
185
def after_run_success (self , content : typing .Any ):
186
+ """
187
+ Handle the successful completion of the run function.
188
+
189
+ Args:
190
+ content (Any): Content generated by the plugin.
191
+ """
126
192
# avoiding JSON serialization errors for types: File and bytes
127
193
report_content = content
128
194
if isinstance (report_content , typing .List ):
@@ -140,6 +206,12 @@ def after_run_success(self, content: typing.Any):
140
206
self .report .save (update_fields = ["status" , "report" ])
141
207
142
208
def log_error (self , e ):
209
+ """
210
+ Log an error encountered during the run function.
211
+
212
+ Args:
213
+ e (Exception): The exception to log.
214
+ """
143
215
if isinstance (
144
216
e , (* self .get_exceptions_to_catch (), SoftTimeLimitExceeded , HTTPError )
145
217
):
@@ -151,6 +223,12 @@ def log_error(self, e):
151
223
logger .exception (error_message )
152
224
153
225
def after_run_failed (self , e : Exception ):
226
+ """
227
+ Handle the failure of the run function.
228
+
229
+ Args:
230
+ e (Exception): The exception that caused the failure.
231
+ """
154
232
self .report .errors .append (str (e ))
155
233
self .report .status = self .report .Status .FAILED
156
234
self .report .save (update_fields = ["status" , "errors" ])
@@ -246,6 +324,12 @@ def _monkeypatch(cls, patches: list = None) -> None:
246
324
@classmethod
247
325
@property
248
326
def python_module (cls ) -> PythonModule :
327
+ """
328
+ Get the Python module associated with the plugin.
329
+
330
+ Returns:
331
+ PythonModule: The Python module instance.
332
+ """
249
333
valid_module = cls .__module__ .replace (str (cls .python_base_path ), "" )
250
334
# remove the starting dot
251
335
valid_module = valid_module [1 :]
@@ -255,9 +339,24 @@ def python_module(cls) -> PythonModule:
255
339
256
340
@classmethod
257
341
def update (cls ) -> bool :
342
+ """
343
+ Update the plugin. Must be implemented by subclasses.
344
+
345
+ Returns:
346
+ bool: Whether the update was successful.
347
+ """
258
348
raise NotImplementedError ("No update implemented" )
259
349
260
350
def _get_health_check_url (self , user : User = None ) -> typing .Optional [str ]:
351
+ """
352
+ Get the URL for performing a health check.
353
+
354
+ Args:
355
+ user (User): The user instance.
356
+
357
+ Returns:
358
+ typing.Optional[str]: The health check URL.
359
+ """
261
360
params = (
262
361
self ._config .parameters .annotate_configured (self ._config , user )
263
362
.annotate_value_for_user (self ._config , user )
@@ -274,6 +373,15 @@ def _get_health_check_url(self, user: User = None) -> typing.Optional[str]:
274
373
return None
275
374
276
375
def health_check (self , user : User = None ) -> bool :
376
+ """
377
+ Perform a health check for the plugin.
378
+
379
+ Args:
380
+ user (User): The user instance.
381
+
382
+ Returns:
383
+ bool: Whether the health check was successful.
384
+ """
277
385
url = self ._get_health_check_url (user )
278
386
if url and url .startswith ("http" ):
279
387
if settings .STAGE_CI or settings .MOCK_CONNECTIONS :
@@ -296,6 +404,9 @@ def health_check(self, user: User = None) -> bool:
296
404
raise NotImplementedError ()
297
405
298
406
def disable_for_rate_limit (self ):
407
+ """
408
+ Disable the plugin due to rate limiting.
409
+ """
299
410
logger .info (f"Trying to disable for rate limit { self } " )
300
411
if self ._user .has_membership ():
301
412
org_configuration = self ._config .get_or_create_org_configuration (
0 commit comments