Override or Disable pyRevit Exception Window

Hi all,

I’ve rolled out a bunch of custom tools to folks in my department and things have been great. I have tried to built in as much code safety as I can with preventing exceptions and catching them if they occur, etc, but still yet sometimes they occur and then the end user is presented with the big pyRevit output window that contains the IronPython traceback information.

I would very much like to override or outright disable this from happening. Ideally, I could choose to either fail silently or just instead display my own message box that is tailored to our use case.

Does anyone have any ideas for how to achieve this?

For reference, this behavior is what I’m referencing:

If you want to replace it with your own message you could just encapsulate your whole script in a big try/except statement, not great practice but it would allow you to call a pyrevit.forms.alert or similar with your own message?

1 Like

Look into how ef tools does this, they have their own try except class that has options to pass or raise exceptions.

Whilst you cant blanket catch/ignore all of your errors you can at least build around steps with likely failing routes. Over time you will just want to build in extra safety and catches though.

Lately I close my program in one function that’s called on startup. Once it allows me to terminate it when the correct condition are not met. Secondly it’s more friendly to use joshuabudarick suggestion to close it within try/except:

trx = DB.Transaction(doc, "transaction_1")

trx.Start()

try:
    MainProgram()
except:
    ReportError()

trx.Commit()

Hi there!

If you use pyrevit’s own transaction you automatically get transaction start and commit (if everything is ok) or rollback (if an exception occurred)

from pyrevit.revit import Transaction

with Transaction():
    # do your thing

You can also pass to the class constructor some parameter to silence the errors.

Generally speaking, though, silencing all kind of exceptions is a really bad Idea on many levels, first and foremost by an user experience point of view: if the user doesn’t see anything, rest assured that he/she will try to click the button again and again because it is expected to do something.

It will also make troubleshooting and fixing bugs harder, especially within pyrevit where you cannot use a debugger to run your code line by line and see what caused the exception.

The best practice for python is to wrap the smallest amount of code inside a try/except block and intercept specific exceptions that are most likely to occur.
(Unfortunately pyrevit code base is not a great example of this practice, as there are many places with big try blocks that would need a bit of rework)

Another way is the “Look before you leap” paradigm (as opposed to the “Easier ask for forgiveness than permission” done via try/except blocks): add all the necessary checks before calling a function/method (for example to ensure that the variable type is correct, if a list is not empty and so on…).

I know this sounds like a lot of work; many of us coders went through the pursuit of the holy grail of flawless code execution with little exception handling, but trust me that your future self will thank you for the extra time you’ve invested on these checks :wink:

1 Like

Ideally, I could choose to either fail silently or just instead display my own message box that is tailored to our use case.

The way to disable the pyRevit exception window is exactly what you describe above, which is to say:

Catch the exception.

Then, you can specify whether to fail silently or display your own message. It seems like you are expecting to have this option without taking the step of catching each exception, but there is no way to display a message box tailored to your use case if you don’t know what use case caused the exception.

I don’t blame you at all for wanting to fail silently, although, I would say it is better to fail quietly rather than silently. This is what I do: Nearly every step of my script/function is preceded by a check like shown in this pseudocode:

def my_function():

    if some_variable != what_i_expect:
        log('ERROR: some_variable was not as expected')
    else:
    
        second_step = do_next_step(some_variable)
        if second_step > expected_maximum_value:
            log('WARNING: second_step did not succeed for element {}'.format(element.Name))
        else:
        
            try:
               result = operation_that_may_fail(second_step)
            except:
                log('ERROR: operation failed on element {}'.format(element.Name))
            else:
            
                log('INFO: operation suceeded for element {}!'.format(element.Name))
                return result

So you see, when something fails, the typical result is that nothing happens. Yet, you are not left in the dark about it because every possible failure gets logged by whatever logging mechanism you choose to employ. During testing and debugging, you enable the log. Then, when you are ready to deploy, disable it so your users don’t need to sit there watching something print out.

It sounds like a lot of work to write all these checks and error messages, but if you write them into your code from the start, it saves you so much time trying to nail down bugs. It becomes easy to know what’s happening at every major operation. The checks prevent most exceptions from happening, in the process notifying you that a particular element didn’t pass.

For logging, you could use pyRevit’s logger, but that tends to print a HUGE amount of info related to the inner workings of pyRevit, which is irrelevant for your purposes. You could use python’s logging module. I have tried it, but it seems like overkill to me and is a bit complex to set up, so I went with my own toggleable print function like so:

GLOBAL_DEBUG = True

# Toggleable print function. Turn on to debug.
def p(*to_print):
	if GLOBAL_DEBUG:
		for item in to_print:
			print item

if 2 + 2 == 4:
    p('All is well.')
else:
    p('The universe is f*****!', 'Abandon all hope!')

Here is an example function:

def show_element(input_object):
	if isinstance(input_object, ListItem):
		element = input_object.elem
		list_item = input_object
	elif isinstance(input_object, Element):
		element = input_object
	else: p('ERROR: input_object was unsupported type in show_element')
	
	if not element: p('element was None') # do lots of checks like this to make sure you are not passing in a null argument.
	else:
		global uidoc
		# Acquire tag's OwnerView and open it before ShowElements() to avoid pop-up dialog
		active_view = uidoc.ActiveView
		try: owner_view_id = element.OwnerViewId
		except: p('ERROR: Element is not valid, possibly deleted.')
		else:
			
			# If element's OwnerView is not already open, open it to avoid popup dialog, then show element.
			if active_view.Id != owner_view_id:
				owner_view = doc.GetElement(owner_view_id)
				uidoc.ActiveView = owner_view
			
			# Check if element is visible in view before showing, to prevent UI unresponsiveness due to popup dialog.
			if not list_item.is_visible_in_view:
				message = 'WARNING: Tag is not visible in view. Cannot show_element.'
				p(message)
			else:
				# Show the element
				uidoc.ShowElements(element)

After a small amount of testing, you will discover the likely exceptions and come up with a way of handling them. Any remaining exceptions (which will be few and far between) will be brought to your attention when your users see the ugly red exception window. This is a good thing. It will enable you to check the log and fix the issue for good.

I think the system I described should accomplish your goals. It fails safely. It fails quietly, but if you listen closely, you can still hear what went wrong.

3 Likes

one note on this: if you check the documentation of the functions and method you’re using, especially Revit API and .NET, it will tell you what exception it throws and under which conditions, so you can build robust code right from the start by avoiding those conditions or using try/except blocks that checks those specific exceptions.

Given the dynamic typing nature of python, though, your life will be a bit harder and will incur in many TypeErrors if you don’t pay enough attention on the type of the parameters you pass to a function.
This is why I always use typing annotations and static type checking with mypy in my python 3 projects. Unfortunately IronPython doesn’t support that, so you just have to pay extra attention on what is stored in your variables.