Bug 1063766 - Python bindings write to the terminal during library calls, confusing the end user
Summary: Python bindings write to the terminal during library calls, confusing the end...
Keywords:
Status: CLOSED CANTFIX
Alias: None
Product: Virtualization Tools
Classification: Community
Component: libvirt-python
Version: unspecified
Hardware: Unspecified
OS: Unspecified
unspecified
unspecified
Target Milestone: ---
Assignee: Libvirt Maintainers
QA Contact:
URL:
Whiteboard:
Depends On:
Blocks:
TreeView+ depends on / blocked
 
Reported: 2014-02-11 12:06 UTC by Robie Basak
Modified: 2019-09-13 14:49 UTC (History)
1 user (show)

Fixed In Version:
Doc Type: Bug Fix
Doc Text:
Clone Of:
Environment:
Last Closed: 2014-02-11 12:17:49 UTC
Embargoed:


Attachments (Terms of Use)

Description Robie Basak 2014-02-11 12:06:51 UTC
Description of problem:

The libvirt Python module writes to stderr on errors by default, which it should not do.


Version-Release number of selected component (if applicable): 1.2.1


How reproducible: always


Steps to Reproduce:

Start with a an empty volume pool called "default". Then, in Python:

>>> import libvirt
>>> c = libvirt.open('qemu:///system')
>>> p = c.storagePoolLookupByName('default')
>>> p.storageVolLookupByName('foo')


Actual results:

The first line in the output was to stderr; generated by the library and not the REPL.

libvirt: Storage Driver error : Storage volume not found: no storage vol with matching name 'foo'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/dist-packages/libvirt.py", line 2937, in storageVolLookupByName
    if ret is None:raise libvirtError('virStorageVolLookupByName() failed', pool=self)
libvirt.libvirtError: Storage volume not found: no storage vol with matching name 'foo'


Expected results:

Libaries should never write to the terminal directly. I understand that the C library does this, but also provides a way to override error handling, and this capability is passed to Python through libvirt.registerErrorHandler. This makes sense in C, since C doesn't have any other standard method for passing back complex error details. C expects you to DIY, and libvirt has done this.

However, in Python, the standard Pythonic way to handle errors is with exceptions only. By the principle of least surprise, I expect all error data to be encapsulated in an exception object, and nothing written to the terminal, by default.

An error condition as far as a library is concerned isn't necessarily an error for the application that uses the library. Thus, arbitrary output (from the user's perspective) to the terminal is confusing to the user.

As an example: I am using storageVolLookupByName as a test to see if a named volume exists. A negative result of such a test is an error to the library, but not an error to my application.


Workaround:

After importing libvirt, calling:

    libvirt.registerErrorHandler(lambda _: None, None)

turns off the default behaviour of writing to stderr.


Thoughts on implementation

The Python bindings could set the C libvirt API's error handler to something that simply saves the error message. When returning from an API call, it could check for a saved error message and instantiate a libvirtError object that includes this information.

IMHO, the ability for the caller to set a custom error handler is less useful in Python, since the caller can always catch and handle exceptions instead. However, if you did want to maintain this ability, it would be possible to do so by storing the callback handler outside the C API, and calling it from my proposed handling above.

Comment 1 Daniel Berrangé 2014-02-11 12:17:49 UTC
I totally agree that libraries should not log to stderr by default. Sadly this unfortunate design decision was made in libvirt's past and for reasons of application compatibility we cannot now change it. ie any console applications which are using libvirt and relying on this default mode of error reporting would be broken if we disabled it.

Our recommendation is simply that all users of libvirt-python apply the workaround that you've suggested above of setting a no-op error callback, and then just use exceptions which already do the right thing.

Comment 2 Robie Basak 2019-09-13 14:49:51 UTC
My workaround broke when switching to the Python 3 bindings with:

    TypeError: <lambda>() takes 1 positional argument but 2 were given

It seems that the callback is now being called with more arguments. My new workaround is:

    def noop(*args, **kwargs): pass
    libvirt.registerErrorHandler(noop, None)


Note You need to log in before you can comment on or make changes to this bug.