issue #72: second attempt WIP

issue72
David Wilson 6 years ago
parent 0e98cd4590
commit 68cf29c284

31
a.py

@ -0,0 +1,31 @@
import mitogen.core
import mitogen.error
@mitogen.main()
def main(router):
ve = ValueError('eep')
ke = KeyError('eep')
cve = mitogen.core.CallError.from_exception(ve)
kve = mitogen.core.CallError.from_exception(ke)
print([cve, type(cve)])
print([kve])
mve = mitogen.error.match(ValueError)
assert isinstance(cve, mve)
assert not isinstance(kve, mve)
print
print
print
print
try:
raise cve
except mve:
pass

@ -252,11 +252,11 @@ class CallError(Error):
#: the :class:`CallError` was constructed with a string argument. #: the :class:`CallError` was constructed with a string argument.
type_name = None type_name = None
@classmethod @staticmethod
def with_type_name(cls, type_name): def for_type_name(type_name):
return type('CallError_' + type_name, (cls,), { # Overridden by mitogen.error on import to dynamically produce
'type_name': type_name, # CallError subclasses reflecting the original exception hierarchy.
}) return CallError
@classmethod @classmethod
def from_exception(cls, e=None): def from_exception(cls, e=None):
@ -269,7 +269,7 @@ class CallError(Error):
if tb: if tb:
s += '\n' s += '\n'
s += ''.join(traceback.format_tb(tb)) s += ''.join(traceback.format_tb(tb))
return cls.with_type_name(type_name)(s) return cls.for_type_name(type_name)(s)
def __reduce__(self): def __reduce__(self):
return (_unpickle_call_error, (self.type_name, self.args[0],)) return (_unpickle_call_error, (self.type_name, self.args[0],))
@ -283,7 +283,7 @@ def _unpickle_call_error(type_name, s):
if not (type(s) is UnicodeType and len(s) < 10000): if not (type(s) is UnicodeType and len(s) < 10000):
raise TypeError('cannot unpickle CallError: bad message') raise TypeError('cannot unpickle CallError: bad message')
if type_name: if type_name:
return CallError.with_type_name(type_name)(s) return CallError.for_type_name(type_name)(s)
return CallError(s) return CallError(s)

@ -34,62 +34,54 @@ parent class in the hierarchy of the the supplied Exception type.
""" """
from __future__ import absolute_import from __future__ import absolute_import
import sys
import mitogen.core import mitogen.core
#: Map Exception class -> Matcher class. #: Map Exception class -> dynamic CallError subclass.
_matcher_by_cls = {} _error_by_cls = {}
def get_matching_classes(cls):
"""
Given a class, return a list containing it and any base classes, operating
recursively such that the returned list contains the entire hierarchy of
`cls`.
"""
classes = [cls]
for subcls in cls.__bases__:
classes.extend(get_matching_classes(subcls))
return classes
class MatcherMeta(type):
def __subclasscheck__(matcher_cls, cls):
print('lol2')
return (
issubclass(cls, mitogen.core.CallError) and
(cls.type_name in matcher_cls.type_names)
)
def __instancecheck__(matcher_cls, e): def find_class_by_qualname(s):
print('lol') modname, sep, classname = s.rpartition('.')
return MatcherMeta.__subclasscheck__(matcher_cls, type(e)) if not sep:
return None
module = sys.modules.get(modname)
if module is None:
return None
__metaclass__ = MatcherMeta return getattr(module, classname, None)
class Matcher: def for_type(cls):
""" try:
This class serves only as a placeholder for its relationship with return _error_by_cls[cls]
:meth:`MatcherMeta.__instancecheck__` where the magic happens. A except KeyError:
dynamically generated Matcher subclass is returned by :func:`match`, to pass
served only for use with isinstance() internally by the Python exception
handling implementation::
# Create a dynamic subclass of Matcher to match mitogen.core.CallError
# instances according to the type of the original exception.
matcher_cls = mitogen.error.match(ValueError)
try:
context.call(func_raising_some_exc)
# Here Python calls type(matcher_class).__instancecheck__(e): bases = tuple(for_type(c) for c in cls.__bases__
except matcher_cls as e: if c is not object)
# e remains bound to the CallError as before.
pass type_name = mitogen.core.qualname(cls)
""" klass = type('CallError_' + type_name, bases, {
#: Overridden by subclasses generated by :func:`match`. 'type_name': type_name,
classes = frozenset([]) })
_error_by_cls[cls] = klass
print [klass, klass.__bases__]
return klass
def for_type_name(type_name):
cls = find_class_by_qualname(type_name)
if cls:
return for_type(cls)
return mitogen.core.CallError
mitogen.core.CallError.for_type_name = staticmethod(for_type_name)
def match(target_cls): def match(target_cls):
@ -110,6 +102,7 @@ def match(target_cls):
:returns: :returns:
:class:`Matcher` subclass. :class:`Matcher` subclass.
""" """
return for_type(target_cls)
try: try:
return _matcher_by_cls[target_cls] return _matcher_by_cls[target_cls]
except KeyError: except KeyError:

@ -0,0 +1,57 @@
import unittest2
import mitogen.core
import mitogen.error
import testlib
class MagicValueError(ValueError):
pass
def func_throws_value_error(*args):
raise ValueError(*args)
def func_throws_value_error_subclass(*args):
raise MagicValueError(*args)
class MatchTest(testlib.RouterMixin, testlib.TestCase):
def _test_no_match(self):
context = self.router.fork()
try:
context.call(func_throws_value_error_subclass)
except mitogen.error.match(ValueError):
pass
def test_no_match(self):
self.assertRaises(mitogen.core.CallError,
lambda: self._test_no_match())
def test_builtin_match(self):
context = self.router.fork()
try:
context.call(func_throws_value_error)
except mitogen.error.match(ValueError):
pass
def test_direct_custom_match(self):
context = self.router.fork()
try:
context.call(func_throws_value_error_subclass)
except mitogen.error.match(MagicValueError):
pass
def test_indirect_match(self):
context = self.router.fork()
try:
context.call(func_throws_value_error_subclass)
except mitogen.error.match(ValueError):
pass
if __name__ == '__main__':
unittest2.main()
Loading…
Cancel
Save