A custom exception class that closely resembles Python’s OSError
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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