Exception Reporting in Aspose.Cells for Python via .NET

Currently Aspose.Cells for Python via .NET only raises a RunTimeError exception to report exceptions thrown by the underlying .NET library.

Are users expected to catch this error and check the start of the message to determine what exception was actually raised and determine the real problem? Is there any additional metadata attached to the Exception we can use to determine what happened? Is there any way to change this behavior?

Are there any plans to improve the library to raise a Python exception equivalent to the underlying exception?

Thanks,
Kyle

@kmonson
When encountering exceptions while processing files, Aspose.Cells products have their own exception handling mechanism. Please check the following apis.

https://reference.aspose.com/cells/python-net/aspose.cells/cellsexception/
https://reference.aspose.com/cells/python-net/aspose.cells/exceptiontype/

If the program throws a runtime error while processing a file, it is likely that the program has encountered a runtime exception. If you have any questions or confusion, would you like to provide your sample file and test code? We will check it soon.

@kmonson
Hi, here I provide an example to illustrate.
1 . For the following .NET code:

public static void NetExceptionTest()
{
	try
	{
		Workbook workbook = new Workbook();
		Cells cells = workbook.Worksheets[0].Cells;
		cells.SetColumnWidth(0, -0.2);
	}
	catch (Exception e)
	{
		Console.WriteLine(e.GetType() + " : " + e.Message);
	}
}

It throws the following exception message: Aspose.Cells.CellsException : Column width must be between 0 and 255
2 . For the corresponding Python code:

def python_exception_test():
    try:
        workbook = Workbook()
        worksheet = workbook.worksheets[0]
        cells = worksheet.cells
        cells.set_column_width(0, -0.2)
    except Exception as e:
        print(e)

Exception message : Proxy error(CellsException): Column width must be between 0 and 255,
This indicates the type and information of the exception.

Since the CellsException class inherits from the .NET framework, Python cannot have an inheritance or derivation relationship with these classes. It must use its own exception classes such as RuntimeError, and so on.

Hope helps a bit!

@xinya.zhu

Thanks you for the example code and for your reply.

I must respectfully disagree with a few of the assertions in your answer.

“This indicates the type and information of the exception.” (emphasis mine)

Without having extensive knowledge of all the possible error messages it’s not realistically possible to derive which ExceptionType was in the original exception from the RunTimeError exception message. In the example given I’m not sure which exception type that is. Is that a Limitation? If so, how many other exception messages could one receive for that type of exception?

This is extra troublesome where there are multiple possible exception types from the same action that might require different responses in code.

“Since the CellsException class inherits from the .NET framework, Python cannot have an inheritance or derivation relationship with these classes. It must use its own exception classes such as RuntimeError, and so on.”

While technically true there are ways to overcome this in a Python library.

The simplest thing I can think of is to (only in the case of the .NET backend to the Python wrapper, not for the general .NET version of Cells) add the enum value of the Exception Type to the start of the Message part of the exception before raising. On the Python side catch any RunTimeError anywhere one could possibly be raised, parse out the original message and exception type, and reraise a CellsException with all the correct info.

For some reason I cannot instantiate a new CellsException instance in Python. That would need to be suppressed in order to create a new normal Python class for that exception. I’m guessing you are using pythonnet for integration with .NET based on the way this library behaves. In order to avoid a conflict with the existing .NET CellsException class you can hide it from Python with the PyExport attribute in .NET. PyExport · pythonnet/pythonnet Wiki · GitHub

@xinya.zhu
Thanks for your info.
We will study it as soon as possible.

@kmonson
Hello, thank you very much for your opinions and suggestions. After internal discussions, we proposed several solutions, such as defining a Python type like ProxyError, and allowing users to get the wrapper object of the internal .NET exception through a property of ProxyError. Of course, we will also take your solution and other possible options into consideration. We have already created an internal ticket: “Full-scale” connecting .NET exceptions machinery to Python, CELLSPYTHONNET-213. As soon as there are any updates, we will notify you immediately.
Thanks!

@xinya.zhu @simon.zhao
Thanks for your replies and willingness to look into this.

I would ask for one thing specific to the Python Exception interface (this may also apply to .NET but it’s not my primary area of expertise) of Cells. It would be very helpful for the interface to raise a different exception class for each underlying Exception Type. This works more naturally with how Python developers expect to work with exceptions and results in cleaner code.

If the resulting interface mirrors the current .NET interface I would need to handle exceptions like this:

try:
    # Do Something in Cells
    pass
except CellsException as e:
    if e.code == ExceptionType.Formula:
        # Handle formula error
        pass
    elif e.code == ExceptionType.InvalidData:
        # Handle data error
        pass
    else:
        # Not something we can deal with here, probably a showstopper.
        # Forgetting to do this will hide errors!
        raise e

Ideally you’d want to write your exception handler like this:

try:
    # Do Something in Cells
    pass
except CellsExceptionFormula as e:
    # Handle formula error
    pass
except CellsExceptionInvalidData as e:
    # Handle data error
    pass

The advantage of the second is that while it’s cleaner it there is also no risk that I forget to reraise an unhandled exception type and swallow the error unintentionally.

Internally in the Cells Python code, once you have the value of ExceptionType and the message it’s pretty easy to setup a map to give you the correct Exception object type to raise.

Something like this:

class _CellsException(Exception):
    code = None
    def __init__(self, message: str):
        super().__init__(message)

    @property
    def message(self):
        return self.args[0]

    @property
    def code(self):
        return self.code

_exception_map = {}

# Create and raise exception
def raise_cell_exception(code: aspose.cells.ExceptionType, message: str):
    klass = _exception_map[code]
    raise klass(message)

# Creat Exception classes from Exception Types
for _e in aspose.cells.ExceptionType:
    #Build CamelCase name
    name = "Cells" + _e.name.title().replace("_", "") + "Error"
    # Create new class with correct value for klass.code.
    klass = type(name, (_CellsException,), dict(code=_e))
    # Add to module namespace for code completion, etc.
    globals()[name] = klass
    # Add to map for simple lookup later
    _exception_map[_e] = klass

Whenever you have an exception to raise from the backend you can do this:

>>> raise_cell_exception(aspose.cells.ExceptionType.CHART, "test")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in raise_cell_exception
CellsChartError: 'test'

Thanks again for looking at this. This will make it easier for me to justify updating our Cells license to my manager.

@kmonson
Hi, thank you very much for your suggestion. Are you proposing to implement the exception handling mechanism in the following way?

try:
    # Do Something in Cells
    pass
except CellsExceptionFormula as e:
    # Handle formula error
    pass
except CellsExceptionInvalidData as e:
    # Handle data error
    pass

If that’s the case, I will have internal discussions (with the wrapper team) and consider incorporating your suggestion.
Thank you!

@xinya.zhu
Yes. That is what I’m asking for. If I can do my exception handling in that manner I’ll be very happy.

As I was writing my previous post I forgot to update the names of the exception in that example to match other changes.

It should read like this (I think these Exception names are better that what I originally suggested):

try:
    # Do Something in Cells
    pass
except CellsFormulaError as e:
    # Handle formula error
    pass
except CellsInvalidDataError as e:
    # Handle data error
    pass

Thanks again.

@kmonson
Okay, we will evaluate your request and continue to record it in the ticket CELLSPYTHONNET-213.