Index: Doc/library/asynchat.rst =================================================================== --- Doc/library/asynchat.rst (revisione 84314) +++ Doc/library/asynchat.rst (copia locale) @@ -114,7 +114,15 @@ channel will consume this producer's data by calling its :meth:`more` method and send the data to the remote endpoint. +.. method:: async_chat.push_callable(function, args=(), kwargs={}) + Takes a callable object and adds it to the producer fifo associated with + the channel. When all currently-pushed producers have been exhausted the + channel will call ``function`` with ``args`` and ``kwargs`` as arguments + (if specified). + + .. versionadded:: 3.2 + .. method:: async_chat.set_terminator(term) Sets the terminating condition to be recognized on the channel. ``term`` Index: Lib/asynchat.py =================================================================== --- Lib/asynchat.py (revisione 84314) +++ Lib/asynchat.py (copia locale) @@ -209,6 +209,10 @@ self.producer_fifo.append(producer) self.initiate_send() + def push_callable (self, fun, *args, **kwargs): + self.producer_fifo.append(_Callable(fun, *args, **kwargs)) + self.initiate_send() + def readable (self): "predicate for inclusion in the readable for select()" # cannot use the old predicate, it violates the claim of the @@ -235,6 +239,9 @@ ## print("first is None") self.handle_close() return + elif hasattr(first, '__call__'): + first() + return ## print("first is not None") # handle classic producer behavior @@ -334,3 +341,19 @@ while l and not haystack.endswith(needle[:l]): l -= 1 return l + + +class _Callable: + + def __init__(self, fun, *args, **kwargs): + self._fun = fun + self._args = args + self._kwargs = kwargs + + def __call__(self): + self._fun(*self._args, **self._kwargs) + + def __bool__(self): + return False + + Index: Lib/test/test_asynchat.py =================================================================== --- Lib/test/test_asynchat.py (revisione 84314) +++ Lib/test/test_asynchat.py (copia locale) @@ -89,6 +89,9 @@ self.contents.append(self.buffer) self.buffer = b"" + def handle_error(self): + raise + def start_echo_server(): event = threading.Event() s = echo_server(event) @@ -107,6 +110,7 @@ self._threads = support.threading_setup() def tearDown (self): + asyncore.close_all(ignore_all=True) support.threading_cleanup(*self._threads) def line_terminator_check(self, term, server_chunk): @@ -234,7 +238,31 @@ # (which could still result in the client not having received anything) self.assertGreater(len(s.buffer), 0) + def test_push_callable(self): + def fun(): + flags.append(None) + def fun_w_arg(arg): + flags.append(arg) + + def fun_w_kwarg(kwarg=None): + flags.append(kwarg) + + flags = [] + s, event = start_echo_server() + c = echo_client(b'\n', s.port) + c.push(b"hello world\n\nI'm not dead yet!\n") + c.push_callable(fun) + c.push_callable(fun_w_arg, 'arg') + c.push_callable(fun_w_kwarg, kwarg='kwarg') + c.push(SERVER_QUIT) + asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01) + s.join() + self.assertEqual(flags, [None, 'arg', 'kwarg']) + self.assertEqual(c.contents, + [b"hello world", b"", b"I'm not dead yet!"]) + + class TestAsynchat_WithPoll(TestAsynchat): usepoll = True