vortexpy 4.0.23__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. vortex/DataWrapTuple.py +27 -0
  2. vortex/DeferUtil.py +224 -0
  3. vortex/Jsonable.py +304 -0
  4. vortex/Payload.py +115 -0
  5. vortex/PayloadEndpoint.py +216 -0
  6. vortex/PayloadEndpointTest.py +164 -0
  7. vortex/PayloadEnvelope.py +166 -0
  8. vortex/PayloadFilterKeys.py +15 -0
  9. vortex/PayloadIO.py +152 -0
  10. vortex/PayloadPriority.py +11 -0
  11. vortex/PayloadResponse.py +173 -0
  12. vortex/PayloadResponseTest.py +39 -0
  13. vortex/PayloadTest.py +136 -0
  14. vortex/PerformTestActionTuple.py +30 -0
  15. vortex/SerialiseUtil.py +273 -0
  16. vortex/TestTuple.py +61 -0
  17. vortex/Tuple.py +968 -0
  18. vortex/TupleAction.py +72 -0
  19. vortex/TupleActionVortex.py +5 -0
  20. vortex/TupleDataForTest.py +25 -0
  21. vortex/TuplePerfTest.py +71 -0
  22. vortex/TuplePolymorphicTupleTypeFieldArgTest.py +74 -0
  23. vortex/TupleRestfulTest.py +140 -0
  24. vortex/TupleSelector.py +105 -0
  25. vortex/TupleTest.py +127 -0
  26. vortex/VortexABC.py +55 -0
  27. vortex/VortexClientHttp.py +308 -0
  28. vortex/VortexClientHttpTest.py +71 -0
  29. vortex/VortexClientTcp.py +355 -0
  30. vortex/VortexClientWebsocketFactory.py +383 -0
  31. vortex/VortexConnectionABC.py +88 -0
  32. vortex/VortexFactory.py +932 -0
  33. vortex/VortexPayloadProtocol.py +180 -0
  34. vortex/VortexServer.py +367 -0
  35. vortex/VortexServerConnection.py +127 -0
  36. vortex/VortexServerHttpResource.py +193 -0
  37. vortex/VortexServerTcp.py +104 -0
  38. vortex/VortexServerWebsocket.py +287 -0
  39. vortex/VortexTcpMemoryLeakPayloadTest.py +611 -0
  40. vortex/VortexTcpMemoryLeakRawTest.py +87 -0
  41. vortex/VortexTest.py +22 -0
  42. vortex/VortexUtil.py +314 -0
  43. vortex/VortexWritePushProducer.py +321 -0
  44. vortex/__init__.py +10 -0
  45. vortex/data_loader/TupleDataLiveLockManager.py +383 -0
  46. vortex/data_loader/TupleDataLoader.py +326 -0
  47. vortex/data_loader/TupleDataLoaderDelegate.py +90 -0
  48. vortex/data_loader/TupleDataLoaderTupleABC.py +10 -0
  49. vortex/data_loader/TupleDataLoaderTuples.py +53 -0
  50. vortex/data_loader/__init__.py +0 -0
  51. vortex/event_bus/TupleChangeEventABC.py +15 -0
  52. vortex/event_bus/TupleChangeEventBus.py +192 -0
  53. vortex/event_bus/TupleChangeEventBusObserverABC.py +11 -0
  54. vortex/event_bus/__init__.py +0 -0
  55. vortex/handler/TupleActionProcessor.py +270 -0
  56. vortex/handler/TupleActionProcessorProxy.py +218 -0
  57. vortex/handler/TupleActionProcessorTestHandler.py +27 -0
  58. vortex/handler/TupleDataActionClient.py +67 -0
  59. vortex/handler/TupleDataObservableCache.py +201 -0
  60. vortex/handler/TupleDataObservableHandler.py +371 -0
  61. vortex/handler/TupleDataObservableProxyHandler.py +340 -0
  62. vortex/handler/TupleDataObservableTestHandler.py +60 -0
  63. vortex/handler/TupleDataObserverClient.py +158 -0
  64. vortex/handler/VortexJSTupleLoaderTestHandler.py +42 -0
  65. vortex/handler/__init__.py +0 -0
  66. vortex/restful/GzippedDataHttpClient.py +170 -0
  67. vortex/restful/GzippedDataHttpClientTest.py +103 -0
  68. vortex/restful/GzippedPayloadHttpClient.py +181 -0
  69. vortex/restful/RestfulHttpClient.py +107 -0
  70. vortex/restful/RestfulHttpClientTest.py +60 -0
  71. vortex/restful/RestfulResource.py +153 -0
  72. vortex/restful/RestfulResourceTest.py +100 -0
  73. vortex/restful/__init__.py +0 -0
  74. vortex/rpc/RPC.py +446 -0
  75. vortex/rpc/RPCTest.py +12 -0
  76. vortex/rpc/__init__.py +0 -0
  77. vortex/sqla_orm/OrmCrudHandler.py +431 -0
  78. vortex/sqla_orm/TupleProviderForDb.py +42 -0
  79. vortex/sqla_orm/VortexTupleJson.py +24 -0
  80. vortex/sqla_orm/__init__.py +0 -0
  81. vortex/storage/TupleStorageSqlite.py +359 -0
  82. vortex/storage/__init__.py +0 -0
  83. vortexpy-4.0.23.dist-info/LICENSE +21 -0
  84. vortexpy-4.0.23.dist-info/METADATA +43 -0
  85. vortexpy-4.0.23.dist-info/RECORD +87 -0
  86. vortexpy-4.0.23.dist-info/WHEEL +5 -0
  87. vortexpy-4.0.23.dist-info/top_level.txt +1 -0
@@ -0,0 +1,60 @@
1
+ import logging
2
+ from datetime import datetime
3
+
4
+ import pytz
5
+ from twisted.internet import task
6
+
7
+ from vortex.DeferUtil import deferToThreadWrapWithLogger
8
+ from vortex.Payload import Payload
9
+ from vortex.TestTuple import TestTuple
10
+ from vortex.TupleDataForTest import makeTestTupleData
11
+ from vortex.TupleSelector import TupleSelector
12
+ from vortex.handler.TupleDataObservableHandler import (
13
+ TuplesProviderABC,
14
+ TupleDataObservableHandler,
15
+ )
16
+
17
+ logger = logging.getLogger(__name__)
18
+ testTuples1Selector = TupleSelector("testTuples1", {"count": 4})
19
+
20
+ testTuples2Selector = TupleSelector("testTuples2", {"count": 7})
21
+
22
+
23
+ class TestTupleProvider(TuplesProviderABC):
24
+ @deferToThreadWrapWithLogger(logger)
25
+ def makeVortexMsg(self, filt: dict, tupleSelector: TupleSelector) -> bytes:
26
+ count = tupleSelector.selector["count"]
27
+ tuples = makeTestTupleData(count)
28
+
29
+ for t in tuples:
30
+ t.aDate = datetime.now(pytz.utc)
31
+ t.aDict = tupleSelector.selector
32
+ t.aString = tupleSelector.name
33
+
34
+ return (
35
+ Payload(filt=filt, tuples=tuples)
36
+ .makePayloadEnvelope()
37
+ .toVortexMsg()
38
+ )
39
+
40
+
41
+ class NotifyTestTimer:
42
+ @classmethod
43
+ def __notify(cls):
44
+ observableHandler.notifyOfTupleUpdate(testTuples1Selector)
45
+ observableHandler.notifyOfTupleUpdate(testTuples2Selector)
46
+
47
+ @classmethod
48
+ def startTupleUpdateNotifyer(cls):
49
+ cls.__loopingCall = task.LoopingCall(cls.__notify)
50
+ d = cls.__loopingCall.start(2)
51
+ d.addErrback(lambda f: logger.exception(f.value))
52
+ d.addCallback(
53
+ lambda _: logger.debug("Observable tuple updates started")
54
+ )
55
+
56
+
57
+ observableHandler = TupleDataObservableHandler("vortexTestObservable")
58
+ observableHandler.addTupleProvider(TestTuple.tupleName(), TestTupleProvider())
59
+ observableHandler.addTupleProvider("testTuples1", TestTupleProvider())
60
+ observableHandler.addTupleProvider("testTuples2", TestTupleProvider())
@@ -0,0 +1,158 @@
1
+ import logging
2
+ from copy import copy
3
+ from typing import List
4
+
5
+ from reactivex.subject import Subject
6
+ from reactivex import operators as ops
7
+ from twisted.internet.defer import Deferred, inlineCallbacks
8
+
9
+ from vortex.DeferUtil import vortexLogFailure
10
+ from vortex.Payload import Payload
11
+ from vortex.PayloadEndpoint import PayloadEndpoint
12
+ from vortex.PayloadEnvelope import PayloadEnvelope
13
+ from vortex.PayloadResponse import PayloadResponse
14
+ from vortex.TupleSelector import TupleSelector
15
+ from vortex.VortexFactory import VortexFactory
16
+ from vortex.handler.TupleDataObservableCache import TupleDataObservableCache
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class TupleDataObserverClient(TupleDataObservableCache):
22
+ def __init__(
23
+ self,
24
+ destVortexName,
25
+ observableName,
26
+ additionalFilt=None,
27
+ observerName="default",
28
+ ):
29
+ """Constructor
30
+
31
+ :param observableName: The name of this observable
32
+ :param additionalFilt: Any additional filter keys that are required
33
+ :param destVortexName: The dest vortex name to send the payloads to
34
+
35
+ """
36
+ TupleDataObservableCache.__init__(self)
37
+
38
+ self._destVortexName = destVortexName
39
+ self._observableName = observableName
40
+ self._observerName = observerName
41
+
42
+ self._sendFilt = dict(
43
+ name=observableName,
44
+ observerName=observerName,
45
+ key="tupleDataObservable",
46
+ )
47
+
48
+ self._listenFilt = dict(name=observableName, key="tupleDataObservable")
49
+
50
+ if additionalFilt:
51
+ self._sendFilt.update(additionalFilt)
52
+ self._listenFilt.update(additionalFilt)
53
+
54
+ self._endpoint = PayloadEndpoint(
55
+ self._listenFilt,
56
+ self._receivePayload,
57
+ acceptOnlyFromVortex=destVortexName,
58
+ )
59
+
60
+ # There are no online checks for the vortex
61
+ # isOnlineSub = statusService.isOnline
62
+ # .filter(online= > online == = true)
63
+ # .subscribe(online= > self.vortexOnlineChanged())
64
+ #
65
+ # self.onDestroyEvent.subscribe(() = > isOnlineSub.unsubscribe())
66
+ VortexFactory.subscribeToVortexStatusChange(destVortexName).pipe(ops.filter(
67
+ lambda online: online is True
68
+ )).subscribe(self._vortexOnlineChanged)
69
+
70
+ TupleDataObservableCache.start(self)
71
+
72
+ def shutdown(self):
73
+ self._endpoint.shutdown()
74
+ TupleDataObservableCache.shutdown(self)
75
+
76
+ def pollForTuples(
77
+ self, tupleSelector: TupleSelector, logTimeoutError: bool = True
78
+ ) -> Deferred:
79
+ startFilt = copy(self._sendFilt)
80
+ startFilt.update({"subscribe": False, "tupleSelector": tupleSelector})
81
+
82
+ def updateCacheCallback(
83
+ payloadEnvelope: PayloadEnvelope,
84
+ ) -> PayloadEnvelope:
85
+ cache, _ = self._updateCache(payloadEnvelope)
86
+ return payloadEnvelope
87
+
88
+ pr = PayloadResponse(
89
+ payloadEnvelope=PayloadEnvelope(startFilt),
90
+ destVortexName=self._destVortexName,
91
+ logTimeoutError=logTimeoutError,
92
+ )
93
+ pr.addCallback(updateCacheCallback)
94
+ pr.addCallback(
95
+ lambda payloadEnvelope: payloadEnvelope.decodePayloadDefer()
96
+ )
97
+ pr.addCallback(lambda payload: payload.tuples)
98
+ return pr
99
+
100
+ def subscribeToTupleSelector(self, tupleSelector: TupleSelector) -> Subject:
101
+ cache = self._makeCache(tupleSelector)
102
+ self._tellServerWeWantData([tupleSelector])
103
+ return cache.subject
104
+
105
+ def _vortexOnlineChanged(self, *args) -> None:
106
+ self._tellServerWeWantData(self._tupleSelectors())
107
+
108
+ @inlineCallbacks
109
+ def _receivePayload(self, payloadEnvelope: PayloadEnvelope, **kwargs):
110
+ # If this message is for a specific observer, and it's not us, then discard it.
111
+ filtObserverName = payloadEnvelope.filt.get("observerName")
112
+ if (
113
+ filtObserverName is not None
114
+ and filtObserverName != self._observerName
115
+ ):
116
+ return
117
+
118
+ # If this message is an error response, then don't process it.
119
+ if payloadEnvelope.result not in (None, True):
120
+ logger.error(
121
+ "Vortex responded with error : %s" % payloadEnvelope.result
122
+ )
123
+ logger.error(str(payloadEnvelope.filt))
124
+ return
125
+
126
+ tupleSelector: TupleSelector = payloadEnvelope.filt["tupleSelector"]
127
+
128
+ if not self._hasTupleSelector(tupleSelector):
129
+ return
130
+
131
+ cache, requiredUpdate = self._updateCache(payloadEnvelope)
132
+ if not requiredUpdate:
133
+ return
134
+
135
+ payload = yield Payload().fromEncodedPayloadDefer(cache.encodedPayload)
136
+ cache.subject.on_next(payload.tuples)
137
+
138
+ def _tellServerWeWantData(self, tupleSelectors: List[TupleSelector]):
139
+ for tupleSelector in tupleSelectors:
140
+ self._sendRequestToServer(
141
+ PayloadEnvelope(
142
+ {"subscribe": True, "tupleSelector": tupleSelector}
143
+ )
144
+ )
145
+
146
+ def _sendRequestToServer(self, payload):
147
+ payload.filt.update(self._sendFilt)
148
+ d = VortexFactory.sendVortexMsg(
149
+ vortexMsgs=payload.toVortexMsg(),
150
+ destVortexName=self._destVortexName,
151
+ )
152
+ d.addErrback(vortexLogFailure, logger, consumeError=True)
153
+
154
+ def _sendUnsubscribeToServer(self, tupleSelector: TupleSelector):
155
+ payload = PayloadEnvelope()
156
+ payload.filt["tupleSelector"] = tupleSelector
157
+ payload.filt["unsubscribe"] = True
158
+ self._sendRequestToServer(payload)
@@ -0,0 +1,42 @@
1
+ # import logging
2
+ #
3
+ # from vortex.PayloadFilterKeys import plDeleteKey
4
+ # from vortex.test.TestTuple import TestTuple
5
+ # from vortex.test.TupleDataForTest import makeTestTupleData
6
+ #
7
+ # logger = logging.getLogger(__name__)
8
+ #
9
+ #
10
+ # # If there were reallying going to a DB, we'd use OrmCrudHandler
11
+ # class VortexJSTupleLoaderTestHandler(ModelHandler):
12
+ # def buildModel(self, payload, **kwargs):
13
+ # logger.debug("Received payload with %s tuples and filt=%s",
14
+ # len(payload.tuples), payload.filt)
15
+ #
16
+ # data = []
17
+ #
18
+ # if payload.tuples:
19
+ # # Return nothing if this was a delete
20
+ # if plDeleteKey in payload.filt:
21
+ # # Return nothing, it was deleted
22
+ # pass
23
+ #
24
+ # else:
25
+ # # Else this was a save, just update some data and return id
26
+ # data = payload.tuples
27
+ # for testTuple in data:
28
+ # testTuple.aInt = (testTuple.aInt if testTuple.aInt else 0) + 10
29
+ # testTuple.aBoolTrue = not (testTuple.aBoolTrue
30
+ # if testTuple.aBoolTrue
31
+ # else 0)
32
+ #
33
+ # else:
34
+ # # Else this is to get new data.
35
+ # data = makeTestTupleData()
36
+ #
37
+ # return data
38
+ #
39
+ #
40
+ # __handler = VortexJSTupleLoaderTestHandler({
41
+ # "key": "vortex.tuple-loader.test.data"
42
+ # })
File without changes
@@ -0,0 +1,170 @@
1
+ import gzip
2
+ import logging
3
+ import struct
4
+ from datetime import datetime
5
+ from io import BytesIO
6
+ from typing import Union, List, Type
7
+
8
+ import pytz
9
+ from twisted.internet import reactor
10
+ from twisted.internet.defer import Deferred, inlineCallbacks
11
+ from twisted.internet.defer import succeed
12
+ from twisted.internet.protocol import Protocol
13
+ from twisted.web._newclient import ResponseDone
14
+ from twisted.web.client import Agent, GzipDecoder, ContentDecoderAgent
15
+ from twisted.web.http_headers import Headers
16
+ from twisted.web.iweb import IBodyProducer, UNKNOWN_LENGTH
17
+ from zope.interface import implementer
18
+
19
+ from vortex.Tuple import Tuple
20
+ from vortex.restful.RestfulResource import HTTP_REQUEST
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class GzippedDataHttpClient:
26
+ """A Restful http client that sends out a binary payload"""
27
+
28
+ DEFAULT_HEADERS = {
29
+ "User-Agent": ["vortex/1.0"],
30
+ "Content-Type": ["application/json"],
31
+ }
32
+
33
+ def __init__(
34
+ self,
35
+ url: Union[str, bytes],
36
+ payload: bytes,
37
+ headers: dict[str, List[str]],
38
+ method: HTTP_REQUEST,
39
+ meta: Type[Tuple],
40
+ isPayloadGzipped: bool = True,
41
+ compressed: bool = True,
42
+ timeout: float = 10,
43
+ ):
44
+ self._httpMethod = method.value.encode()
45
+ self._url = url.encode() if isinstance(url, str) else url
46
+ self._payload = payload
47
+ self._headers = {**self.DEFAULT_HEADERS, **headers}
48
+ self._meta = meta
49
+ self._compressed = (
50
+ compressed # send http requests whether gzip-compressed or not
51
+ )
52
+ self._isPayloadGzipped = isPayloadGzipped
53
+ self._timeout = timeout # connect timeout
54
+
55
+ @inlineCallbacks
56
+ def run(self) -> Type[Tuple]:
57
+ agent = Agent(reactor, connectTimeout=self._timeout)
58
+ # Add the gzip decoder
59
+ if self._compressed:
60
+ agent = ContentDecoderAgent(agent, [(b"gzip", GzipDecoder)])
61
+
62
+ binaryPayloadRequestProducer = _BinaryPayloadRequestProducer(
63
+ self._payload,
64
+ self._meta,
65
+ self._isPayloadGzipped,
66
+ )
67
+ # Make the web request
68
+ try:
69
+ response = yield agent.request(
70
+ self._httpMethod,
71
+ self._url,
72
+ Headers(self._headers),
73
+ binaryPayloadRequestProducer,
74
+ )
75
+ self._meta = binaryPayloadRequestProducer.meta
76
+ except Exception as e:
77
+ self._meta.exceptions.append(e)
78
+ finally:
79
+ if self._meta.exceptions:
80
+ return self._meta
81
+
82
+ self._meta.code = response.code
83
+ self._meta.version = response.version
84
+ self._meta.headers = {
85
+ k.decode(): v[0].decode()
86
+ for k, v in response.headers.getAllRawHeaders()
87
+ }
88
+
89
+ self._meta = yield self._cbResponse(response, self._meta)
90
+ return self._meta
91
+
92
+ def _cbResponse(self, response, meta):
93
+ responseProducer = _ResponseProducer(meta)
94
+ response.deliverBody(responseProducer)
95
+ return responseProducer.asyncMeta
96
+
97
+
98
+ @implementer(IBodyProducer)
99
+ class _BinaryPayloadRequestProducer:
100
+ def __init__(
101
+ self,
102
+ payload: bytes,
103
+ meta: Type[Tuple],
104
+ isPayloadGzipped: bool = True,
105
+ ):
106
+ self._meta = meta
107
+ self._payload = payload
108
+ self._isGzippedAlready = isPayloadGzipped
109
+ self.length = self._getGzipUncompressedSize()
110
+ self._gzipFile = None
111
+
112
+ @property
113
+ def meta(self):
114
+ return self._meta
115
+
116
+ def startProducing(self, consumer):
117
+ self._meta.requestDate = datetime.now(tz=pytz.utc)
118
+
119
+ payload = self._payload
120
+ if self._isGzippedAlready:
121
+ payload = gzip.decompress(self._payload)
122
+
123
+ try:
124
+ consumer.write(payload)
125
+ except Exception as e:
126
+ raise e
127
+
128
+ return succeed(None)
129
+
130
+ def pauseProducing(self):
131
+ pass
132
+
133
+ def stopProducing(self):
134
+ pass
135
+
136
+ def _getGzipUncompressedSize(self) -> int:
137
+ # Uncompressed size is stored in the last 4 bytes of the gzip file.
138
+ # We can read the binary data and convert it to an int.
139
+ # (This will only work for files under 4GB)
140
+ if not self._isGzippedAlready:
141
+ return len(self._payload)
142
+
143
+ with BytesIO(self._payload) as f:
144
+ try:
145
+ return struct.unpack("I", self._payload[-4:])[0]
146
+ except Exception as e:
147
+ # may be invalid format of gzip
148
+ logger.exception(e)
149
+ return UNKNOWN_LENGTH
150
+
151
+
152
+ class _ResponseProducer(Protocol):
153
+ def __init__(self, meta: Type[Tuple]):
154
+ self._finishedDeferred = Deferred()
155
+ self._writeSize = 0
156
+ self._body = b""
157
+ self._meta = meta
158
+
159
+ @property
160
+ def asyncMeta(self):
161
+ return self._finishedDeferred
162
+
163
+ def dataReceived(self, data: bytes):
164
+ self._body += data
165
+
166
+ def connectionLost(self, reason):
167
+ if isinstance(reason.value, ResponseDone):
168
+ self._meta.responseDate = datetime.now(tz=pytz.utc)
169
+ self._meta.body = self._body
170
+ self._finishedDeferred.callback(self._meta)
@@ -0,0 +1,103 @@
1
+ import gzip
2
+ import logging
3
+ from datetime import datetime
4
+ from typing import List
5
+
6
+ from twisted.trial import unittest
7
+ from twisted.internet.error import TimeoutError as TwistedTimeoutError
8
+
9
+ from vortex.Tuple import Tuple, TupleField, addTupleType
10
+ from vortex.restful.GzippedDataHttpClient import GzippedDataHttpClient
11
+ from vortex.restful.RestfulResource import HTTP_REQUEST
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @addTupleType
17
+ class GzippedDataHttpResponseTuple(Tuple):
18
+ __tupleType__ = "GzippedDataHttpResponseTuple"
19
+
20
+ requestDate: datetime = TupleField()
21
+ responseDate: datetime = TupleField()
22
+ code: int = TupleField(defaultValue=0)
23
+ version: tuple = TupleField() # (b'HTTP', 1, 1)
24
+ headers: dict[str, str] = TupleField()
25
+ body: bytes = TupleField()
26
+ exceptions: List[Exception] = TupleField(defaultValue=[])
27
+
28
+
29
+ class GzippedDataHttpClientTest(unittest.TestCase):
30
+ def setUp(self):
31
+ self.payload = gzip.compress(b"test")
32
+ self.headers = {}
33
+ self.meta = GzippedDataHttpResponseTuple()
34
+
35
+ def testHttpPost(self):
36
+ client = GzippedDataHttpClient(
37
+ url="http://httpbin.org/post",
38
+ payload=self.payload,
39
+ headers=self.headers,
40
+ method=HTTP_REQUEST.POST,
41
+ meta=GzippedDataHttpResponseTuple(),
42
+ isPayloadGzipped=True,
43
+ compressed=True,
44
+ )
45
+ d = client.run()
46
+ d.addCallback(self._assertValidResponse)
47
+ return d
48
+
49
+ def testHttpsPost(self):
50
+ client = GzippedDataHttpClient(
51
+ url="https://httpbin.org/post",
52
+ payload=self.payload,
53
+ headers=self.headers,
54
+ method=HTTP_REQUEST.POST,
55
+ meta=GzippedDataHttpResponseTuple(),
56
+ isPayloadGzipped=True,
57
+ compressed=True,
58
+ )
59
+ d = client.run()
60
+ d.addCallback(self._assertValidResponse)
61
+ return d
62
+
63
+ def testPlaintextDataPost(self):
64
+ payload = gzip.decompress(self.payload)
65
+ client = GzippedDataHttpClient(
66
+ url="http://httpbin.org/post",
67
+ payload=payload,
68
+ headers=self.headers,
69
+ method=HTTP_REQUEST.POST,
70
+ meta=GzippedDataHttpResponseTuple(),
71
+ isPayloadGzipped=False,
72
+ compressed=True,
73
+ )
74
+ d = client.run()
75
+ d.addCallback(self._assertValidResponse)
76
+ return d
77
+
78
+ def testConnectTimeout(self):
79
+ client = GzippedDataHttpClient(
80
+ url="http://example.com:19839",
81
+ payload=self.payload,
82
+ headers=self.headers,
83
+ method=HTTP_REQUEST.POST,
84
+ meta=GzippedDataHttpResponseTuple(),
85
+ isPayloadGzipped=False,
86
+ compressed=True,
87
+ timeout=1.0,
88
+ )
89
+ d = client.run()
90
+ d.addCallback(self._assertTimeoutException)
91
+ return d
92
+
93
+ def _assertValidResponse(self, tuple_: GzippedDataHttpResponseTuple):
94
+ self.assertEqual(tuple_.code, 200)
95
+ self.assertTrue(
96
+ (tuple_.responseDate - tuple_.requestDate).total_seconds() > 0
97
+ )
98
+
99
+ def _assertTimeoutException(self, tuple_: GzippedDataHttpResponseTuple):
100
+ booleans = [
101
+ isinstance(e, TwistedTimeoutError) for e in tuple_.exceptions
102
+ ]
103
+ self.assertTrue(any(booleans))