Skip to content

Instantly share code, notes, and snippets.

@Bachsau

Bachsau/virtual_oserror.py

Last active May 27, 2020
Embed
What would you like to do?
A custom exception class that closely resembles Python’s OSError
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# A custom exception class that closely resembles the behavior of
# Python’s built-in OSError, including arguments and automatic
# subclassing. Useful if a fine-grained error handling based on
# error numbers is needed.
class VirtualOSError(Exception):
"""Base exception for all virtual OS related errors.
This class closely resembles OSError with the exception of different
error codes and lack of the `winerror` attribute. Call signature:
VirtualOSError([arg])
VirtualOSError(errno, strerror[, filename[, filename2]])
"""
__slots__ = ("_characters_written", "errno", "filename", "filename2", "strerror")
__errnomap = None
def __new__(cls, *args):
"""Return a new instance of VirtualOSError or one of its subclasses.
The subclass is chosen based on the value of the first argument,
as long as a second argument is present.
"""
if cls is VirtualOSError and 2 <= len(args) <= 4:
if cls.__errnomap is None:
cls.__errnomap = {
1: SpecialVirtualOSError
# FIXME: Extend as needed
# Python's OSError has a bunch of subclasses,
# some of them covering multiple error numbers.
}
newcls = cls.__errnomap.get(args[0])
if newcls is not None:
return newcls(*args)
self = Exception.__new__(cls, *args)
for attr in VirtualOSError.__slots__:
setattr(self, attr, None)
return self
def __init__(self, *args):
"""Initialize VirtualOSError with the given values."""
a = len(args)
if 2 <= a <= 4:
self.errno = args[0]
self.strerror = args[1]
if a > 2:
self.args = args[:2]
self.filename = args[2]
if a > 3:
self.filename2 = args[3]
def __delattr__(self, attr):
"""Delete the attribute if it’s not a special one, else set it to None."""
if attr in VirtualOSError.__slots__:
setattr(self, attr, None)
else:
Exception.__delattr__(self, attr)
def __str__(self):
"""Return string representation."""
if self.errno is not None and self.strerror is not None:
if self.filename is None:
return "[Errno {!s}] {!s}".format(self.errno, self.strerror)
if self.filename2 is None:
return "[Errno {!s}] {!s}: {!r}".format(self.errno, self.strerror, self.filename)
return "[Errno {!s}] {!s}: {!r} -> {!r}".format(self.errno, self.strerror, self.filename, self.filename2)
return Exception.__str__(self)
@property
def characters_written(self) -> int:
"""Number of characters written before the error occurred."""
if self._characters_written is None:
raise AttributeError("characters_written")
return self._characters_written
@characters_written.setter
def characters_written(self, value: int) -> None:
"""Number of characters written before the error occurred."""
if type(value) is int:
self._characters_written = value
return
try:
value = value.__index__()
except Exception:
# OSError does it more or less the same way
pass
else:
if type(value) is int:
self._characters_written = value
return
raise TypeError("'{}' object cannot be interpreted as an integer".format(type(value).__name__))
@characters_written.deleter
def characters_written(self) -> None:
"""Number of characters written before the error occurred."""
self._characters_written = None
class SpecialVirtualOSError(VirtualOSError):
"""Exception raised under special circumstances.
See help(VirtualOSError) for accurate signature.
"""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.