Skip to content

Commit 67aa160

Browse files
authoredJun 2, 2018
Merge branch 'trunk' into 9460-incremental-codingstandard
2 parents 5b89d0a + c551aa8 commit 67aa160

File tree

5 files changed

+204
-6
lines changed

5 files changed

+204
-6
lines changed
 

‎src/twisted/application/internet.py

+56-4
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
from twisted.internet import task
4949
from twisted.python.failure import Failure
5050
from twisted.internet.defer import (
51-
CancelledError, Deferred, succeed, fail
51+
CancelledError, Deferred, succeed, fail, maybeDeferred
5252
)
5353

5454
from automat import MethodicalMachine
@@ -548,7 +548,8 @@ class _ClientMachine(object):
548548

549549
_machine = MethodicalMachine()
550550

551-
def __init__(self, endpoint, factory, retryPolicy, clock, log):
551+
def __init__(self, endpoint, factory, retryPolicy, clock,
552+
prepareConnection, log):
552553
"""
553554
@see: L{ClientService.__init__}
554555
@@ -566,6 +567,7 @@ def __init__(self, endpoint, factory, retryPolicy, clock, log):
566567
self._factory = factory
567568
self._timeoutForAttempt = retryPolicy
568569
self._clock = clock
570+
self._prepareConnection = prepareConnection
569571
self._connectionInProgress = succeed(None)
570572

571573
self._awaitingConnected = []
@@ -633,10 +635,35 @@ def _connect(self):
633635

634636
self._connectionInProgress = (
635637
self._endpoint.connect(factoryProxy)
638+
.addCallback(self._runPrepareConnection)
636639
.addCallback(self._connectionMade)
637640
.addErrback(self._connectionFailed))
638641

639642

643+
def _runPrepareConnection(self, protocol):
644+
"""
645+
Run any C{prepareConnection} callback with the connected protocol,
646+
ignoring its return value but propagating any failure.
647+
648+
@param protocol: The protocol of the connection.
649+
@type protocol: L{IProtocol}
650+
651+
@return: Either:
652+
653+
- A L{Deferred} that succeeds with the protocol when the
654+
C{prepareConnection} callback has executed successfully.
655+
656+
- A L{Deferred} that fails when the C{prepareConnection} callback
657+
throws or returns a failed L{Deferred}.
658+
659+
- The protocol, when no C{prepareConnection} callback is defined.
660+
"""
661+
if self._prepareConnection:
662+
return (maybeDeferred(self._prepareConnection, protocol)
663+
.addCallback(lambda _: protocol))
664+
return protocol
665+
666+
640667
@_machine.output()
641668
def _resetFailedAttempts(self):
642669
"""
@@ -1009,7 +1036,9 @@ class ClientService(service.Service, object):
10091036
"""
10101037

10111038
_log = Logger()
1012-
def __init__(self, endpoint, factory, retryPolicy=None, clock=None):
1039+
1040+
def __init__(self, endpoint, factory, retryPolicy=None, clock=None,
1041+
prepareConnection=None):
10131042
"""
10141043
@param endpoint: A L{stream client endpoint
10151044
<interfaces.IStreamClientEndpoint>} provider which will be used to
@@ -1029,13 +1058,36 @@ def __init__(self, endpoint, factory, retryPolicy=None, clock=None):
10291058
this attribute will not be serialized, and the default value (the
10301059
reactor) will be restored when deserialized.
10311060
@type clock: L{IReactorTime}
1061+
1062+
@param prepareConnection: A single argument L{callable} that may return
1063+
a L{Deferred}. It will be called once with the L{protocol
1064+
<interfaces.IProtocol>} each time a new connection is made. It may
1065+
call methods on the protocol to prepare it for use (e.g.
1066+
authenticate) or validate it (check its health).
1067+
1068+
The C{prepareConnection} callable may raise an exception or return
1069+
a L{Deferred} which fails to reject the connection. A rejected
1070+
connection is not used to fire an L{Deferred} returned by
1071+
L{whenConnected}. Instead, L{ClientService} handles the failure
1072+
and continues as if the connection attempt were a failure
1073+
(incrementing the counter passed to C{retryPolicy}).
1074+
1075+
L{Deferred}s returned by L{whenConnected} will not fire until
1076+
any L{Deferred} returned by the C{prepareConnection} callable
1077+
fire. Otherwise its successful return value is consumed, but
1078+
ignored.
1079+
1080+
Present Since Twisted NEXT
1081+
1082+
@type prepareConnection: L{callable}
1083+
10321084
"""
10331085
clock = _maybeGlobalReactor(clock)
10341086
retryPolicy = _defaultPolicy if retryPolicy is None else retryPolicy
10351087

10361088
self._machine = _ClientMachine(
10371089
endpoint, factory, retryPolicy, clock,
1038-
log=self._log,
1090+
prepareConnection=prepareConnection, log=self._log,
10391091
)
10401092

10411093

‎src/twisted/application/test/test_internet.py

+145
Original file line numberDiff line numberDiff line change
@@ -1031,3 +1031,148 @@ def test_stopServiceOnStoppedService(self):
10311031

10321032
self.assertIsNone(self.successResultOf(firstStopDeferred))
10331033
self.assertIsNone(self.successResultOf(secondStopDeferred))
1034+
1035+
1036+
def test_prepareConnectionCalledWhenServiceStarts(self):
1037+
"""
1038+
The C{prepareConnection} callable is called after
1039+
L{ClientService.startService} once the connection is made.
1040+
"""
1041+
prepares = [0]
1042+
1043+
def prepareConnection(_proto):
1044+
prepares[0] += 1
1045+
1046+
cq, service = self.makeReconnector(prepareConnection=prepareConnection,
1047+
startService=True)
1048+
self.assertEqual(1, prepares[0])
1049+
1050+
1051+
def test_prepareConnectionCalledWithProtocol(self):
1052+
"""
1053+
The C{prepareConnection} callable is passed the connected protocol
1054+
instance.
1055+
"""
1056+
newProtocols = []
1057+
1058+
def prepareConnection(proto):
1059+
newProtocols.append(proto)
1060+
1061+
cq, service = self.makeReconnector(
1062+
prepareConnection=prepareConnection,
1063+
)
1064+
self.assertIdentical(cq.constructedProtocols[0], newProtocols[0])
1065+
1066+
1067+
def test_prepareConnectionCalledAfterConnectionMade(self):
1068+
"""
1069+
The C{prepareConnection} callback is invoked only once a connection is
1070+
made.
1071+
"""
1072+
prepares = [0]
1073+
1074+
def prepareConnection(_proto):
1075+
prepares[0] += 1
1076+
1077+
clock = Clock()
1078+
cq, service = self.makeReconnector(prepareConnection=prepareConnection,
1079+
fireImmediately=False,
1080+
clock=clock)
1081+
1082+
cq.connectQueue[0].errback(Exception('connection attempt failed'))
1083+
self.assertEqual(0, prepares[0]) # Not called yet.
1084+
1085+
clock.advance(AT_LEAST_ONE_ATTEMPT)
1086+
cq.connectQueue[1].callback(None)
1087+
1088+
self.assertEqual(1, prepares[0]) # Was called.
1089+
1090+
1091+
def test_prepareConnectionCalledOnReconnect(self):
1092+
"""
1093+
The C{prepareConnection} callback is invoked each time a connection is
1094+
made, including on reconnection.
1095+
"""
1096+
prepares = [0]
1097+
1098+
def prepareConnection(_proto):
1099+
prepares[0] += 1
1100+
1101+
clock = Clock()
1102+
cq, service = self.makeReconnector(prepareConnection=prepareConnection,
1103+
clock=clock)
1104+
1105+
self.assertEqual(1, prepares[0]) # Called once.
1106+
1107+
# Protocol disconnects.
1108+
cq.constructedProtocols[0].connectionLost(Failure(IndentationError()))
1109+
clock.advance(AT_LEAST_ONE_ATTEMPT)
1110+
1111+
self.assertEqual(2, prepares[0]) # Called again.
1112+
1113+
1114+
def test_prepareConnectionReturnValueIgnored(self):
1115+
"""
1116+
The C{prepareConnection} return value is ignored when it does not
1117+
indicate a failure. Even though the callback participates in the
1118+
internal new-connection L{Deferred} chain for error propagation
1119+
purposes, any successful result does not affect the ultimate return
1120+
value.
1121+
"""
1122+
# Sentinel object returned by prepareConnection.
1123+
sentinel = object()
1124+
1125+
def prepareConnection(proto):
1126+
return sentinel
1127+
1128+
cq, service = self.makeReconnector(prepareConnection=prepareConnection)
1129+
1130+
result = self.successResultOf(service.whenConnected())
1131+
self.assertNotIdentical(sentinel, result)
1132+
1133+
1134+
def test_prepareConnectionReturningADeferred(self):
1135+
"""
1136+
The C{prepareConnection} callable returns a deferred and calls to
1137+
L{ClientService.whenConnected} wait until it fires.
1138+
"""
1139+
newProtocols = []
1140+
newProtocolDeferred = Deferred()
1141+
1142+
def prepareConnection(proto):
1143+
newProtocols.append(proto)
1144+
return newProtocolDeferred
1145+
1146+
cq, service = self.makeReconnector(prepareConnection=prepareConnection)
1147+
1148+
whenConnectedDeferred = service.whenConnected()
1149+
self.assertNoResult(whenConnectedDeferred)
1150+
1151+
newProtocolDeferred.callback(None)
1152+
1153+
self.assertIdentical(cq.applicationProtocols[0],
1154+
self.successResultOf(whenConnectedDeferred))
1155+
1156+
1157+
def test_prepareConnectionThrows(self):
1158+
"""
1159+
The connection attempt counts as a failure when the
1160+
C{prepareConnection} callable throws.
1161+
"""
1162+
clock = Clock()
1163+
1164+
def prepareConnection(_proto):
1165+
raise IndentationError()
1166+
1167+
cq, service = self.makeReconnector(prepareConnection=prepareConnection,
1168+
clock=clock)
1169+
1170+
whenConnectedDeferred = service.whenConnected(failAfterFailures=2)
1171+
self.assertNoResult(whenConnectedDeferred)
1172+
1173+
clock.advance(AT_LEAST_ONE_ATTEMPT)
1174+
self.assertNoResult(whenConnectedDeferred)
1175+
1176+
clock.advance(AT_LEAST_ONE_ATTEMPT)
1177+
self.assertIdentical(IndentationError,
1178+
self.failureResultOf(whenConnectedDeferred).type)

‎src/twisted/conch/scripts/ckeygen.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ def changePassPhrase(options):
189189
'Enter file in which the key is (%s): ' % filename)
190190
try:
191191
key = keys.Key.fromFile(options['filename'])
192-
except keys.EncryptedKeyError as e:
192+
except keys.EncryptedKeyError:
193193
# Raised if password not supplied for an encrypted key
194194
if not options.get('pass'):
195195
options['pass'] = getpass.getpass('Enter old passphrase: ')

‎src/twisted/internet/test/test_win32serialport.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
try:
1919
from twisted.internet import serialport
2020
import serial
21-
except ImportError as e:
21+
except ImportError:
2222
serialport = None
2323
serial = None
2424

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
twisted.application.internet.ClientService now accepts a function to initialize or validate a connection before it is returned by the whenConnected method as the prepareConnection argument.

0 commit comments

Comments
 (0)