From 3453d4d7d0b9bf0224c6a2178249a353a68dc962 Mon Sep 17 00:00:00 2001 From: Jesse London Date: Wed, 5 Sep 2018 16:41:05 -0500 Subject: [PATCH] Python 3 support for classmethod call targets There were two problems with detection and handling of class methods as call targets in Python 3: * Methods no longer define `im_self` -- this is now only `__self__` * The `types` module no longer defines a `ClassType` The universally-compatible (v2.6+) solution was to switch to using the `inspect` module -- whose interface has been stable -- and to checking the method attribute `__self__`. (It doesn't hurt that `inspect` checks are more brief and we now no longer need the `types` module here.) --- mitogen/parent.py | 6 ++---- tests/call_function_test.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/mitogen/parent.py b/mitogen/parent.py index ca57efea..0dfc4f07 100644 --- a/mitogen/parent.py +++ b/mitogen/parent.py @@ -47,7 +47,6 @@ import termios import textwrap import threading import time -import types import zlib # Absolute imports for <2.5. @@ -490,9 +489,8 @@ def upgrade_router(econtext): def make_call_msg(fn, *args, **kwargs): - if isinstance(fn, types.MethodType) and \ - isinstance(fn.im_self, (type, types.ClassType)): - klass = mitogen.core.to_text(fn.im_self.__name__) + if inspect.ismethod(fn) and inspect.isclass(fn.__self__): + klass = mitogen.core.to_text(fn.__self__.__name__) else: klass = None diff --git a/tests/call_function_test.py b/tests/call_function_test.py index f0074258..eb83dff5 100644 --- a/tests/call_function_test.py +++ b/tests/call_function_test.py @@ -36,7 +36,17 @@ def func_accepts_returns_sender(sender): return sender +class TargetClass: + + offset = 100 + + @classmethod + def add_numbers_with_offset(cls, x, y): + return cls.offset + x + y + + class CallFunctionTest(testlib.RouterMixin, testlib.TestCase): + def setUp(self): super(CallFunctionTest, self).setUp() self.local = self.router.fork() @@ -44,6 +54,12 @@ class CallFunctionTest(testlib.RouterMixin, testlib.TestCase): def test_succeeds(self): self.assertEqual(3, self.local.call(function_that_adds_numbers, 1, 2)) + def test_succeeds_class_method(self): + self.assertEqual( + self.local.call(TargetClass.add_numbers_with_offset, 1, 2), + 103, + ) + def test_crashes(self): exc = self.assertRaises(mitogen.core.CallError, lambda: self.local.call(function_that_fails))