-1

Situation

The xlwings package provides a convenient way to call python functions from an excel VBA module. The xlwings documentation gives the following basic example:

Write the code below into a VBA module.

Sub HelloWorld()
    RunPython ("import hello; hello.world()")
End Sub

This calls the following code in hello.py:

# hello.py
import numpy as np
import xlwings as xw

def world():
    wb = xw.Book.caller()
    wb.sheets[0].range('A1').value = 'Hello World!'

Trying to run the python function world() directly (instead of calling it from excel VBA) gives the following error message:

Exception: Book.caller() must not be called directly. Call through Excel or set a mock caller first with Book.set_mock_caller().

Question

I would like to modify the world() function such that it raises a custom exception instead when being run directly. In order to achieve this I first need to determine programmatically whether the world() function is being run directly or being called from excel VBA (at least that's what I'd think). How could I do this?

Xukrao
  • 8,003
  • 5
  • 26
  • 52
  • https://docs.python.org/3/tutorial/errors.html#handling-exceptions – Stop harming Monica Jun 21 '19 at 23:54
  • @Goyo The exception that would have to be caught here has no specific type (it's a generic `Exception`), meaning that I also can't specify a specifc exception type in a try-except clause. Aren't such catch-all try-except clauses usually [highly discouraged](https://stackoverflow.com/a/14797508/7306999)? – Xukrao Jun 22 '19 at 00:10

1 Answers1

1

You can catch the exception and then raise your own:

def world():
    try:
        wb = xw.Book.caller()
    except Exception:
        raise CustomException(custom_message)
    wb.sheets[0].range('A1').value = 'Hello World!'

You are worried that Exception is too generic, and rightly so. But that's xlwings' fault, not yours. If it is raising a generic Exception that's all you are left with to catch. You could check the exception message to make sure that you are not catching the wrong exception, but that would be brittle. Error messages are usually undocumented and not to be regarded as public, stable API.

Alternatively you can fix the problem where the problem is, xlwings' source code, and make it do what looks to me like the right thing to do: raising a more specific exception.

class NotFromExcelError(Exception):
    pass

And at the end of caller:

raise NotFromExcelError('Book.caller() must not be called directly. Call through Excel '
                        'or set a mock caller first with Book.set_mock_caller().')

I hope a pull request like this would be accepted because raising a bare Exception like it currently does looks really wrong.

Stop harming Monica
  • 12,141
  • 1
  • 36
  • 56
  • 1
    Very clear explanation, thanks. It looks like the need for a more specific exception type was already recognized as early as 2015 (see [github issue](https://github.com/xlwings/xlwings/issues/322)), but that thus far it hasn't been implemented yet. – Xukrao Jun 22 '19 at 15:32
  • 1
    The lack of further activity on that reinforces my impression that the best way of getting this fixed is doing the work yourself. It is an easy change after all and it shouldn't be hard having it merged. – Stop harming Monica Jun 22 '19 at 16:27
  • 1
    A [github pull request](https://github.com/xlwings/xlwings/pull/1123) that could resolve the issue was made, but it is currently still pending. – Xukrao Aug 20 '19 at 01:39
  • 1
    Update: the pull request was rejected. The developer wanted to instead implement custom exceptions across the entire package in one go. As of now this hasn't happend yet (the issue is listed on the [xlwings 1.0 roadmap](https://github.com/xlwings/xlwings/issues/1993)). – Xukrao Aug 27 '23 at 21:39