-1

There are two functions: one downloads the excel file (ExcelFileUploadView(APIView)) and the other processes the downloaded file(def parse_excel_rfi_sheet). Function parse_excel_rfi_sheet is called inside ExcelFileUploadView(APIView)

class ExcelFileUploadView(APIView):
    parser_classes = (MultiPartParser, FormParser)
    permission_classes = (permissions.AllowAny,)

    def put(self, request, format=None):
        if 'file' not in request.data:
            raise ParseError("Empty content")
        f = request.data['file']
        filename = f.name
        if filename.endswith('.xlsx'):
            try:
                file = default_storage.save(filename, f)
                r = parse_excel_rfi_sheet(file)
                status = 200
            except:
                raise Exception({"general_errors": ["Error during file upload"]})
            finally:
                default_storage.delete(file)
        else:
            status = 406
            r = {"general_errors": ["Please upload only xlsx files"]}
        return Response(r, status=status) 

def parse_excel_rfi_sheet(file):
    workbook = load_workbook(filename=file)
    sheet = workbook["RFI"]
    curent_module_coordinate = []
    try:
        ....
        curent_module_coordinate.append(sheet['E688'].value)  
        curent_module_coordinate.append(sheet['E950'].value)  
        if check_exel_rfi_template_structure(structure=curent_module_coordinate):
            file_status = True
        else:
            file_status = False
    except:
        raise Exception({"general_errors": ["Error during excel file parsing. Unknown module cell"]})

The problem is that when an error occurs inside the parse_excel_rfi_sheet, I do not see a call of {"general_errors": ["Error during excel file parsing. Unknown module cell"]} Instead, I always see the call

{"general_errors": ["Error during file upload"]}

That's why I can't understand at what stage the error occurred: at the moment of downloading the file or at the moment of processing. How to change this?

Jekson
  • 2,892
  • 8
  • 44
  • 79

3 Answers3

1

Since you are calling parse_excel_rfi_sheet from ExcelFileUploadView whenever the exception {"general_errors": ["Error during excel file parsing. Unknown module cell"]} is raised from parse_excel_rfi_sheet function try block from ExcelFileUploadView fails and comes to except and raises the exception {"general_errors": ["Error during file upload"]}.

You can verify this by printing the exception raised by the ExcelFileUploadView function. Chane the try block to the following:

try:
    file = default_storage.save(filename, f)
    r = parse_excel_rfi_sheet(file)
    status = 200
except Exception as e:
    print("Exception raised ", e)
    raise Exception({"general_errors": ["Error during file upload"]})
Prudhvi
  • 1,095
  • 1
  • 7
  • 18
1

Your problem comes from catching absolutely all exceptions, first in parse_excel_rfi_sheet, then once again in your put method. Both bare except clause (except: whatever_code_here) and large try blocks are antipatterns - you only want to catch the exact exceptions you're expecting at a given point (using except (SomeExceptionType, AnotherExceptionType, ...) as e:, and have as few code as possible in your try blocks so you are confident you know where the exception comes from.

The only exception (no pun intended) to this rule is the case of "catch all" handlers at a higher level, that are use to catch unexpected errors, log them (so you have a trace of what happened), and present a friendly error message to the user - but even then, you don't want a bare except clause but a except Exception as e.

TL;DR: never assume anything about which exception was raised, where and why, and never pass exceptions silently (at least log them - and check your logs).

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • Thank you, I did a review code according to your addition and now I get a specific error instead of a general message. – Jekson Apr 09 '20 at 10:01
0

raise Exception(...) generates a new Exception instance and raises that one. This means, the try ... except in put effectively throws away the exception it caught and replaces it with a new one with message "Error during file upload", which is why you always see the same message.

A clean way to handle this would be to define a custom subclass of Exception (e.g., InvalidFormatException) and raise that one in parse_excel_rfi_sheet, having two different except cases in put:

class InvalidFormatException(Exception):
    pass

[...]

def parse_excel_rfi_sheet(file):
    workbook = load_workbook(filename=file)
    sheet = workbook["RFI"]
    curent_module_coordinate = []
    try:
        ....
        curent_module_coordinate.append(sheet['E688'].value)  
        curent_module_coordinate.append(sheet['E950'].value)  
        if check_exel_rfi_template_structure(structure=curent_module_coordinate):
            file_status = True
        else:
            file_status = False
    except:
        raise InvalidFormatException({"general_errors": ["Error during excel file parsing. Unknown module cell"]})

Your put then becomes:

    def put(self, request, format=None):
        if 'file' not in request.data:
            raise ParseError("Empty content")
        f = request.data['file']
        filename = f.name
        if filename.endswith('.xlsx'):
            try:
                file = default_storage.save(filename, f)
                r = parse_excel_rfi_sheet(file)
                status = 200
            except InvalidFormatException:
                raise # pass on the exception
            except:
                raise Exception({"general_errors": ["Error during file upload"]})
            finally:
                default_storage.delete(file)
        else:
            status = 406
            r = {"general_errors": ["Please upload only xlsx files"]}
        return Response(r, status=status)

Warning: As pointed out in the comments to this answer, note that -although not directly inquired- the OP's code should be further modified to remove the bare except: clause, as this is probably not the expected behaviour.

GPhilo
  • 18,519
  • 9
  • 63
  • 89
  • An answer about exception handling that uses bare except clauses is a __very__ bad answer. Proper exception handling rule #1: __never__ use a bare except clause. – bruno desthuilliers Apr 09 '20 at 08:37
  • @brunodesthuilliers the question is not about proper exception handling and I make no claim about my solution being correct in terms of "proper" exception handling, since that's way off-topic. The OP wants to know why his exception is cancelled away, and I explain this providing an example of how to fix this. Delving into the possible fallacies of the OP's code is for https://codereview.stackexchange.com/, not SO. – GPhilo Apr 09 '20 at 08:41
  • @GPhilo Except it sounds like it doesn't work the way OP expected it to, invalidating it for CR. – Mast Apr 09 '20 at 08:44
  • @Mast this question would not belong on CR as it is, of course. I'm just stating that me elaborating on why the OP's code is bad (aside from the requested issue, of course) would be off topic. – GPhilo Apr 09 '20 at 08:46
  • @GPhilo SO is not a forum, it's a QA based technical knowledge base - so your answers must not only address the OP's question, but also be usable as references for other users. And good answers __should__ go past the simple technical answer by mentionning bad and good practices, possible improvements etc. – bruno desthuilliers Apr 09 '20 at 08:52
  • @GPhilo and yes, this question IS about proper exception handling, definitly. – bruno desthuilliers Apr 09 '20 at 08:52
  • @brunodesthuilliers Added a warning for further users. As per "good answers should go past the simple technical answer by mentioning bad and good practices, possible improvements etc.", I disagree. An answer must focus on the posed question, not on all possible fallacies therein. If the OP is interested in a code review, again, there are better-suited places for that. – GPhilo Apr 09 '20 at 09:01
  • 1
    @GPhilo I guess we have to agree to disagree then. But thanks nonetheless for having added a warning. – bruno desthuilliers Apr 09 '20 at 09:10