diff -up Twisted-18.4.0/src/twisted/conch/manhole.py.966 Twisted-18.4.0/src/twisted/conch/manhole.py --- Twisted-18.4.0/src/twisted/conch/manhole.py.966 2018-03-26 10:19:31.000000000 +0200 +++ Twisted-18.4.0/src/twisted/conch/manhole.py 2018-06-30 11:33:16.703721804 +0200 @@ -19,9 +19,11 @@ from io import BytesIO from twisted.conch import recvline from twisted.internet import defer -from twisted.python.compat import _tokenize +from twisted.python.compat import _tokenize, _get_async_param from twisted.python.htmlizer import TokenPrinter + + class FileWrapper: """ Minimal write-file-like object. @@ -151,8 +153,9 @@ class ManholeInterpreter(code.Interactiv return failure - def write(self, data, async=False): - self.handler.addOutput(data, async) + def write(self, data, isAsync=None, **kwargs): + isAsync = _get_async_param(isAsync, **kwargs) + self.handler.addOutput(data, isAsync) @@ -237,14 +240,16 @@ class Manhole(recvline.HistoricRecvLine) return not w.endswith(b'\n') and not w.endswith(b'\x1bE') - def addOutput(self, data, async=False): - if async: + def addOutput(self, data, isAsync=None, **kwargs): + isAsync = _get_async_param(isAsync, **kwargs) + if isAsync: self.terminal.eraseLine() - self.terminal.cursorBackward(len(self.lineBuffer) + len(self.ps[self.pn])) + self.terminal.cursorBackward(len(self.lineBuffer) + + len(self.ps[self.pn])) self.terminal.write(data) - if async: + if isAsync: if self._needsNewline(): self.terminal.nextLine() diff -up Twisted-18.4.0/src/twisted/mail/imap4.py.966 Twisted-18.4.0/src/twisted/mail/imap4.py --- Twisted-18.4.0/src/twisted/mail/imap4.py.966 2018-03-26 10:19:31.000000000 +0200 +++ Twisted-18.4.0/src/twisted/mail/imap4.py 2018-06-30 11:33:16.704721802 +0200 @@ -42,7 +42,7 @@ from twisted.python.compat import ( _bytesChr, unichr as chr, _b64decodebytes as decodebytes, _b64encodebytes as encodebytes, intToBytes, iterbytes, long, nativeString, networkString, unicode, - _matchingString, _PY3 + _matchingString, _PY3, _get_async_param, ) from twisted.internet import interfaces @@ -1090,8 +1090,9 @@ class IMAP4Server(basic.LineReceiver, po self._respond(b'NO', tag, message) - def sendUntaggedResponse(self, message, async=False): - if not async or (self.blocked is None): + def sendUntaggedResponse(self, message, isAsync=None, **kwargs): + isAsync = _get_async_param(isAsync, **kwargs) + if not isAsync or (self.blocked is None): self._respond(message, None, None) else: self._queuedAsync.append(message) @@ -2497,25 +2498,28 @@ class IMAP4Server(basic.LineReceiver, po # def modeChanged(self, writeable): if writeable: - self.sendUntaggedResponse(message=b'[READ-WRITE]', async=True) + self.sendUntaggedResponse(message=b'[READ-WRITE]', isAsync=True) else: - self.sendUntaggedResponse(message=b'[READ-ONLY]', async=True) + self.sendUntaggedResponse(message=b'[READ-ONLY]', isAsync=True) def flagsChanged(self, newFlags): for (mId, flags) in newFlags.items(): encodedFlags = [networkString(flag) for flag in flags] msg = intToBytes(mId) + ( - b' FETCH (FLAGS (' +b' '.join(encodedFlags) + b'))' + b' FETCH (FLAGS (' + b' '.join(encodedFlags) + b'))' ) - self.sendUntaggedResponse(msg, async=True) + self.sendUntaggedResponse(msg, isAsync=True) def newMessages(self, exists, recent): if exists is not None: - self.sendUntaggedResponse(intToBytes(exists) + b' EXISTS', async=True) + self.sendUntaggedResponse( + intToBytes(exists) + b' EXISTS', isAsync=True) if recent is not None: - self.sendUntaggedResponse(intToBytes(recent) + b' RECENT', async=True) + self.sendUntaggedResponse( + intToBytes(recent) + b' RECENT', isAsync=True) + TIMEOUT_ERROR = error.TimeoutError() diff -up Twisted-18.4.0/src/twisted/newsfragments/9384.bugfix.966 Twisted-18.4.0/src/twisted/newsfragments/9384.bugfix --- Twisted-18.4.0/src/twisted/newsfragments/9384.bugfix.966 2018-06-30 11:33:16.682721845 +0200 +++ Twisted-18.4.0/src/twisted/newsfragments/9384.bugfix 2018-06-30 11:33:16.706721798 +0200 @@ -0,0 +1,2 @@ +Syntax error under Python 3.7 fixed for twisted.conch.manhole and +twisted.main.imap4. diff -up Twisted-18.4.0/src/twisted/newsfragments/9384.removal.966 Twisted-18.4.0/src/twisted/newsfragments/9384.removal --- Twisted-18.4.0/src/twisted/newsfragments/9384.removal.966 2018-06-30 11:33:16.706721798 +0200 +++ Twisted-18.4.0/src/twisted/newsfragments/9384.removal 2018-06-30 11:33:16.706721798 +0200 @@ -0,0 +1,4 @@ +async keyword argument is deprecated in twisted.conch.manhole +(ManholeInterpreter.write and Manhole.add) and in +twisted.main.imap4.IMAP4Server.sendUntaggedResponse, +isAsync keyword argument is introduced instead. diff -up Twisted-18.4.0/src/twisted/python/compat.py.966 Twisted-18.4.0/src/twisted/python/compat.py --- Twisted-18.4.0/src/twisted/python/compat.py.966 2018-04-13 15:15:32.000000000 +0200 +++ Twisted-18.4.0/src/twisted/python/compat.py 2018-06-30 11:33:16.704721802 +0200 @@ -31,6 +31,7 @@ import struct import sys import tokenize from types import MethodType as _MethodType +import warnings from io import TextIOBase, IOBase @@ -833,6 +834,34 @@ except ImportError: from collections import Sequence +def _get_async_param(isAsync=None, **kwargs): + """ + Provide a backwards-compatible way to get async param value that does not + cause a syntax error under Python 3.7. + + @param isAsync: isAsync param value (should default to None) + @type isAsync: L{bool} + + @param kwargs: keyword arguments of the caller (only async is allowed) + @type kwargs: L{dict} + + @raise TypeError: Both isAsync and async specified. + + @return: Final isAsync param value + @rtype: L{bool} + """ + if 'async' in kwargs: + warnings.warn( + "'async' keyword argument is deprecated, please use isAsync", + DeprecationWarning, stacklevel=2) + if isAsync is None and 'async' in kwargs: + isAsync = kwargs.pop('async') + if kwargs: + raise TypeError + return bool(isAsync) + + + __all__ = [ "reraise", "execfile", diff -up Twisted-18.4.0/src/twisted/test/test_compat.py.966 Twisted-18.4.0/src/twisted/test/test_compat.py --- Twisted-18.4.0/src/twisted/test/test_compat.py.966 2018-03-26 10:19:31.000000000 +0200 +++ Twisted-18.4.0/src/twisted/test/test_compat.py 2018-06-30 11:33:16.706721798 +0200 @@ -16,7 +16,7 @@ from twisted.python.compat import ( reduce, execfile, _PY3, _PYPY, comparable, cmp, nativeString, networkString, unicode as unicodeCompat, lazyByteSlice, reraise, NativeStringIO, iterbytes, intToBytes, ioType, bytesEnviron, iteritems, - _coercedUnicode, unichr, raw_input, _bytesRepr + _coercedUnicode, unichr, raw_input, _bytesRepr, _get_async_param, ) from twisted.python.filepath import FilePath from twisted.python.runtime import platform @@ -921,3 +921,37 @@ class FutureBytesReprTests(unittest.Test ``b`` to the returned repr on both Python 2 and 3. """ self.assertEqual(_bytesRepr(b'\x00'), "b'\\x00'") + + + +class GetAsyncParamTests(unittest.SynchronousTestCase): + """ + Tests for L{twisted.python.compat._get_async_param} + """ + + def test_get_async_param(self): + """ + L{twisted.python.compat._get_async_param} uses isAsync by default, + or deprecated async keyword argument if isAsync is None. + """ + self.assertEqual(_get_async_param(isAsync=False), False) + self.assertEqual(_get_async_param(isAsync=True), True) + self.assertEqual( + _get_async_param(isAsync=None, **{'async': False}), False) + self.assertEqual( + _get_async_param(isAsync=None, **{'async': True}), True) + self.assertRaises(TypeError, _get_async_param, False, {'async': False}) + + + def test_get_async_param_deprecation(self): + """ + L{twisted.python.compat._get_async_param} raises a deprecation + warning if async keyword argument is passed. + """ + self.assertEqual( + _get_async_param(isAsync=None, **{'async': False}), False) + currentWarnings = self.flushWarnings( + offendingFunctions=[self.test_get_async_param_deprecation]) + self.assertEqual( + currentWarnings[0]['message'], + "'async' keyword argument is deprecated, please use isAsync")