New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Python bindings: proposal for enhanced exception translation #6198
Comments
I started trying to do this as a PR, but I don't understand boost.python very well. I know I need to do something like struct error{};
struct os_error : error{};
class_<os_error, bases<error, BUILTIN_RUNTIME_ERROR, BUILTIN_OSERROR> >(...); What should those macros be to make this work? |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Unstale |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
unstale I found that pybind11 has some robust support for exception translation: https://pybind11.readthedocs.io/en/stable/advanced/exceptions.html I plan to explore pybind11 more fully when I get a chance. |
Current state
Currently the python bindings translate all exceptions to
RuntimeError
.The only way to apply special handling to error conditions is to check the exception message. This makes code awkward, verbose and error-prone.
In particular: in concurrent usage of libtorrent, the
invalid torrent handle used
error can happen on most lines of code, so special error handling is essential.Desired state
The python bindings should raise exceptions from a meaningful type hierarchy.
Proposal
I propose the following exception type hierarchy:
In a future version, we should remove
RuntimeError
from the exception ancestry.The
_
-.prefixed_OSError
tree is intended as a deprecated bridge toRuntimeError
.The exceptions should have the following features:
error_category
.Error
and its subclasses should have avalue
attribute with the error value.OSError
should correspond to bothgeneric_category
andsystem_category
.system_category
on Windows, thewinerror
value should match the error valuesystem_category
on non-Windows, theerrno
should match the error valuegeneric_category
on all platforms, theerrno
should match the error valueAlso the following function:
Additionally:
generic_category
andsystem_category
).Rationale
I considered the following principles:
RuntimeError
in order not to break current usageOSError
corresponds closely to the intended usage of bothgeneric_category
andsystem_category
; that is, for both generic non-system-specific errors and as a cross-platform container for system errorslibtorrent.torrent_info("does-not-exist")
should raise the builtinFileNotFoundError
.libtorrent_category
withinvalid_torrent_handle
generic_category
withECANCELED
libtorrent_category
withduplicate_torrent
In order to adhere to 1. and 3., we must duplicate the hierarchy of the builtin
OSError
, with copies of each subclass that descend from both their counterpart andRuntimeError
. This hierarchy hasn't changed since version 3.3, so I expect this duplication to be a one-time cost.In the future when we remove
RuntimeError
from the ancestry, we can remove the_OSError
hierarchy. Our customCanceledError
can remain. (IfCanceledError
ever becomes a built-in type, ourCanceledError
can become a deprecated alias to that).Our goal is to deprecate catching exceptions with
except RuntimeError:
. However, exception cannot tell how they are caught, so I can't think of a place where we could fireDeprecationWarning
when a user writesexcept RuntimeError:
, versusexcept FileNotFoundError:
. I think the only thing we can do is document thatRuntimeError
is deprecated, and remove it in a major release.Note that I propose a python naming scheme rather than a C++ scheme, as the exception types don't match a class in C++.
Evaluation
I have been evaluating this scheme in tvaf by translating exceptions in python, and I like it a lot. See my ltpy.py
(Since
RuntimeError
only has the error message and not the originalerror_code
, I iterate error messages for all values of all known categories, and map unique error messages to their known (category, value) tuple, and then map that to the exception hierarchy as described above. Fortunately almost all error messages are unique, so this works well)I find it especially invaluable to be able to catch
InvalidTorrentHandleError
.I have actually been using a scheme in which
libtorrent.OSError
is a subclass oflibtorrent.Error
, which gives more weight to the all-exceptions-under-one-ancestor principle. But I have not found any value in doing this, and I have not found any python projects which attempt to map system errors this way. So I proposed a simpler scheme above.The text was updated successfully, but these errors were encountered: