Skip to content

Instantly share code, notes, and snippets.

@Bachsau
Last active May 27, 2020 01:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Bachsau/fa7d18fccf52e6a5d3d40681281543be to your computer and use it in GitHub Desktop.
Save Bachsau/fa7d18fccf52e6a5d3d40681281543be to your computer and use it in GitHub Desktop.
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