1#!/usr/bin/env python
2"""
3
4    exceptions.py
5
6"""
7
8################################################################################
9#
10#   exceptions.py
11#
12#
13#   Copyright (c) 10/9/2009 Leo Goodstadt
14#
15#   Permission is hereby granted, free of charge, to any person obtaining a copy
16#   of this software and associated documentation files (the "Software"), to deal
17#   in the Software without restriction, including without limitation the rights
18#   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19#   copies of the Software, and to permit persons to whom the Software is
20#   furnished to do so, subject to the following conditions:
21#
22#   The above copyright notice and this permission notice shall be included in
23#   all copies or substantial portions of the Software.
24#
25#   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26#   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27#   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28#   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29#   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30#   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31#   THE SOFTWARE.
32#################################################################################
33
34import sys
35import os
36from collections import defaultdict
37
38
39# 88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
40
41#   Exceptions
42
43
44# 88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
45
46# if __name__ != '__main__':
47#    import task
48
49class error_task(Exception):
50    def __init__(self, *errmsg):
51        Exception.__init__(self, *errmsg)
52
53        # list of associated tasks
54        self.tasks = set()
55
56        # error message
57        self.main_msg = ""
58
59    def get_main_msg(self):
60        """
61        Make main message with lists of task names
62        Prefix with new lines for added emphasis
63        """
64        # turn tasks names into 'def xxx(...): format
65        task_names = "\n".join("task = %r" % t._name for t in self.tasks)
66        if len(self.main_msg):
67            return "\n\n" + self.main_msg + " for\n\n%s\n" % task_names
68        else:
69            return "\n\n%s\n" % task_names
70
71    def __str__(self):
72        # indent
73        msg = self.get_main_msg() + " ".join(map(str, self.args))
74        return "    " + msg.replace("\n", "\n    ")
75
76    def specify_task(self, task, main_msg):
77        self.tasks.add(task)
78        self.main_msg = main_msg
79        return self
80
81
82class error_task_contruction(error_task):
83    """
84    Exceptions when contructing pipeline tasks
85    """
86
87    def __init__(self, task, main_msg, *errmsg):
88        error_task.__init__(self, *errmsg)
89        self.specify_task(task, main_msg)
90
91
92class RethrownJobError(error_task):
93    """
94    Wrap up one or more exceptions rethrown across process boundaries
95
96        See multiprocessor.Server.handle_request/serve_client for an analogous function
97    """
98
99    def __init__(self, job_exceptions=[]):
100        error_task.__init__(self)
101        self.job_exceptions = list(job_exceptions)
102
103    def __len__(self):
104        return len(self.job_exceptions)
105
106    def append(self, job_exception):
107        self.job_exceptions.append(job_exception)
108
109    def task_to_func_name(self, task_name):
110        if "mkdir " in task_name:
111            return task_name
112
113        return "def %s(...):" % task_name.replace("__main__.", "")
114
115    def get_nth_exception_str(self, nn=-1):
116        if nn == -1:
117            nn = len(self.job_exceptions) - 1
118        task_name, job_name, exception_name, exception_value, exception_stack = self.job_exceptions[
119            nn]
120        message = "\nException #%d\n" % (nn + 1)
121        message += "  '%s%s' raised in ...\n" % (exception_name, exception_value)
122        if task_name:
123            message += "   Task = %s\n   %s\n\n" % (self.task_to_func_name(task_name),
124                                                    job_name)
125        message += "%s\n" % (exception_stack, )
126        return message.replace("\n", "\n    ")
127
128    def __str__(self):
129        message = ["\nOriginal exception%s:\n" %
130                   ("s" if len(self.job_exceptions) > 1 else "")]
131        for ii in range(len(self.job_exceptions)):
132            message += self.get_nth_exception_str(ii)
133        #
134        #   For each exception:
135        #       turn original exception stack message into an indented string
136        #
137        return (self.get_main_msg()).replace("\n", "\n    ") + "".join(message)
138
139
140class error_input_file_does_not_match(error_task):
141    pass
142
143
144class fatal_error_input_file_does_not_match(error_task):
145    pass
146
147
148class task_FilesArgumentsError(error_task):
149    pass
150
151
152class task_FilesreArgumentsError(error_task):
153    pass
154
155
156class MissingInputFileError(error_task):
157    pass
158
159
160class JobSignalledBreak(error_task):
161    pass
162
163
164class JobSignalledSuspend(error_task):
165    pass
166
167
168class JobSignalledResume(error_task):
169    pass
170
171
172class JobFailed(error_task):
173    pass
174
175
176class PostTaskArgumentError(error_task):
177    pass
178
179
180class JobsLimitArgumentError(error_task):
181    pass
182
183
184class error_task_get_output(error_task_contruction):
185    pass
186
187
188class error_task_transform_inputs_multiple_args(error_task_contruction):
189    pass
190
191
192class error_task_transform(error_task_contruction):
193    pass
194
195
196class error_task_product(error_task_contruction):
197    pass
198
199
200class error_task_mkdir(error_task_contruction):
201    pass
202
203
204class error_task_permutations(error_task_contruction):
205    pass
206
207
208class error_task_combinations(error_task_contruction):
209    pass
210
211
212class error_task_combinations_with_replacement(error_task_contruction):
213    pass
214
215
216class error_task_merge(error_task_contruction):
217    pass
218
219
220class error_task_subdivide(error_task_contruction):
221    pass
222
223
224class error_task_originate(error_task_contruction):
225    pass
226
227
228class error_task_collate(error_task_contruction):
229    pass
230
231
232class error_task_collate_inputs_multiple_args(error_task_contruction):
233    pass
234
235
236class error_task_split(error_task_contruction):
237    pass
238
239
240class error_task_files_re(error_task_contruction):
241    pass
242
243
244class error_task_files(error_task_contruction):
245    pass
246
247
248class error_task_parallel(error_task_contruction):
249    pass
250
251
252class error_making_directory(error_task):
253    pass
254
255
256class error_duplicate_task_name(error_task):
257    pass
258
259
260class error_decorator_args(error_task):
261    pass
262
263
264class error_task_name_lookup_failed(error_task):
265    pass
266
267
268class error_task_decorator_takes_no_args(error_task):
269    pass
270
271
272class error_function_is_not_a_task(error_task):
273    pass
274
275
276class error_ambiguous_task(error_task):
277    pass
278
279
280class error_not_a_pipeline(error_task):
281    pass
282
283
284class error_circular_dependencies(error_task):
285    pass
286
287
288class error_not_a_directory(error_task):
289    pass
290
291
292class error_missing_output(error_task):
293    pass
294
295
296class error_job_signalled_interrupt(error_task):
297    pass
298
299
300class error_node_not_task(error_task):
301    pass
302
303
304class error_missing_runtime_parameter(error_task):
305    pass
306
307
308class error_unescaped_regular_expression_forms(error_task):
309    pass
310
311
312class error_checksum_level(error_task):
313    pass
314
315
316class error_missing_args(error_task):
317    pass
318
319
320class error_too_many_args(error_task):
321    pass
322
323
324class error_inputs_multiple_args(error_task):
325    pass
326
327
328class error_set_input(error_task):
329    pass
330
331
332class error_set_output(error_task):
333    pass
334
335
336class error_no_head_tasks(error_task):
337    pass
338
339
340class error_no_tail_tasks(error_task):
341    pass
342
343
344class error_executable_str(error_task):
345    pass
346
347
348class error_extras_wrong_type(error_task):
349    pass
350
351
352# 88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
353
354#   Testing
355# 88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
356if __name__ == '__main__':
357    import unittest
358
359    #
360    #   minimal task object to test exceptions
361    #
362    class task:
363        class Task (object):
364            """
365            dummy task
366            """
367            _action_mkdir = 1
368
369            def __init__(self, _name,  _action_type=0):
370                self._action_type = _action_type
371                self._name = _name
372
373    class Test_exceptions(unittest.TestCase):
374
375        #       self.assertEqual(self.seq, range(10))
376        #       self.assert_(element in self.seq)
377        #       self.assertRaises(ValueError, random.sample, self.seq, 20)
378
379        def test_error_task(self):
380            """
381                test
382            """
383            fake_task1 = task.Task("task1")
384            fake_task2 = task.Task("task2")
385            fake_mkdir_task3 = task.Task("task3", task.Task._action_mkdir)
386            fake_mkdir_task4 = task.Task("task4", task.Task._action_mkdir)
387            e = error_task()
388            e.specify_task(fake_task1, "Some message 0")
389            e.specify_task(fake_task2, "Some message 1")
390            e.specify_task(fake_mkdir_task3, "Some message 2")
391            e.specify_task(fake_mkdir_task4, "Some message 3")
392            self.assertEqual(str(e),
393                             """
394
395    Some message 3 for
396
397    'def task1(...):'
398    'def task2(...):'
399    task3
400    task4
401    """)
402
403        def test_RethrownJobError(self):
404            """
405                test
406            """
407            #job_name, exception_name, exception_value, exception_stack
408            exception_data = [
409                [
410                    "task1",
411                    "[[temp_branching_dir/a.2, a.1] -> temp_branching_dir/a.3]",
412                    "ruffus.task.MissingInputFileError",
413                    "(instance value)",
414                    "Traceback (most recent call last):\n  File \"what.file.py\", line 333, in some_func\n  somecode(sfasf)\n"
415                ],
416                [
417                    "task1",
418                    "[None -> [temp_branching_dir/a.1, temp_branching_dir/b.1, temp_branching_dir/c.1]]",
419                    "exceptions.ZeroDivisionError:",
420                    "(1)",
421                    "Traceback (most recent call last):\n  File \"anotherfile.py\", line 345, in other_func\n  badcode(rotten)\n"
422                ]
423
424            ]
425            e = RethrownJobError(exception_data)
426            fake_task1 = task.Task("task1")
427            fake_task2 = task.Task("task2")
428            fake_mkdir_task3 = task.Task("task3", task.Task._action_mkdir)
429            fake_mkdir_task4 = task.Task("task4", task.Task._action_mkdir)
430            e.specify_task(fake_task1, "Exceptions running jobs")
431            e.specify_task(fake_task2, "Exceptions running jobs")
432            e.specify_task(fake_mkdir_task3, "Exceptions running jobs")
433            e.specify_task(fake_mkdir_task4, "Exceptions running jobs")
434            self.assertEqual(str(e),
435                             """
436
437    Exceptions running jobs for
438
439    'def task1(...):'
440    'def task2(...):'
441    task3
442    task4
443
444    Original exceptions:
445
446    Exception #1
447    ruffus.task.MissingInputFileError(instance value):
448    for task1.[[temp_branching_dir/a.2, a.1] -> temp_branching_dir/a.3]
449
450    Traceback (most recent call last):
451      File "what.file.py", line 333, in some_func
452      somecode(sfasf)
453
454
455    Exception #2
456    exceptions.ZeroDivisionError:(1):
457    for task1.[None -> [temp_branching_dir/a.1, temp_branching_dir/b.1, temp_branching_dir/c.1]]
458
459    Traceback (most recent call last):
460      File "anotherfile.py", line 345, in other_func
461      badcode(rotten)
462
463    """)
464
465
466#
467#   debug code not run if called as a module
468#
469if __name__ == '__main__':
470    if sys.argv.count("--debug"):
471        sys.argv.remove("--debug")
472    unittest.main()
473