vortexpy 4.1.6__tar.gz → 4.1.7__tar.gz
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.
- {vortexpy-4.1.6 → vortexpy-4.1.7}/PKG-INFO +1 -1
- {vortexpy-4.1.6 → vortexpy-4.1.7}/pyproject.toml +1 -1
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/PayloadEndpoint.py +4 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/PayloadIO.py +9 -2
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/PayloadResponse.py +7 -3
- vortexpy-4.1.7/vortex/PayloadResponse_test.py +42 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/__init__.py +1 -1
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/handler/TupleActionProcessorProxy.py +1 -1
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/handler/TupleDataObservableProxyHandler.py +1 -1
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/handler/TupleDataObserverClient.py +2 -2
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/rpc/RPC.py +48 -14
- vortexpy-4.1.7/vortex/rpc/RPC_test.py +114 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortexpy.egg-info/PKG-INFO +1 -1
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortexpy.egg-info/SOURCES.txt +2 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/README.md +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/setup.cfg +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/DataWrapTuple.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/DeferUtil.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/Jsonable.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/Payload.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/PayloadEndpointTest.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/PayloadEnvelope.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/PayloadFilterKeys.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/PayloadPriority.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/PayloadResponseTest.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/PayloadTest.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/PerformTestActionTuple.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/SerialiseUtil.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/SerialiseUtil_test.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/TestTuple.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/Tuple.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/TupleAction.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/TupleDataForTest.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/TuplePerfTest.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/TuplePolymorphicTupleTypeFieldArgTest.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/TupleRestful.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/TupleRestfulTest.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/TupleSelector.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/TupleTest.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexABC.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexAccessControlContextABC.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexAuthenticatorABC.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexClientHttp.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexClientHttpTest.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexClientTcp.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexClientWebsocketFactory.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexConnectionABC.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexEndpointPermission.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexFactory.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexPayloadProtocol.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexPermission.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexPermissionError.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexServer.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexServerConnection.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexServerHttpResource.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexServerTcp.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexServerWebsocket.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexSession.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexTcpMemoryLeakPayloadTest.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexTcpMemoryLeakRawTest.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexTest.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexUtil.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/VortexWritePushProducer.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/data_loader/TupleDataLiveLockManager.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/data_loader/TupleDataLoader.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/data_loader/TupleDataLoaderDelegate.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/data_loader/TupleDataLoaderTupleABC.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/data_loader/TupleDataLoaderTuples.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/data_loader/__init__.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/event_bus/TupleChangeEventABC.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/event_bus/TupleChangeEventBus.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/event_bus/TupleChangeEventBusObserverABC.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/event_bus/__init__.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/handler/TupleActionProcessor.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/handler/TupleActionProcessorTestHandler.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/handler/TupleDataAccessGranterABC.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/handler/TupleDataActionClient.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/handler/TupleDataObservableCache.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/handler/TupleDataObservableHandler.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/handler/TupleDataObservableTestHandler.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/handler/TupleDataProviderABC.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/handler/VortexJSTupleLoaderTestHandler.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/handler/__init__.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/mypy_plugin.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/py.typed +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/restful/GzippedDataHttpClient.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/restful/GzippedDataHttpClientTest.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/restful/GzippedPayloadHttpClient.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/restful/RestfulHttpClient.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/restful/RestfulHttpClientTest.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/restful/RestfulResource.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/restful/RestfulResourceTest.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/restful/__init__.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/rpc/RPCTest.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/rpc/__init__.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/sqla_orm/OrmCrudHandler.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/sqla_orm/TupleProviderForDb.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/sqla_orm/VortexTupleJson.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/sqla_orm/__init__.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/storage/TupleStorageSqlite.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/storage/__init__.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/tuple_json_schema/__init__.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/tuple_json_schema/constants.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/tuple_json_schema/json_schema_node_tuple.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/tuple_json_schema/tuple_json_schema_builder.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/tuple_json_schema/tuple_json_schema_test.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortex/tuple_json_schema/tuple_json_schema_test_tuples.py +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortexpy.egg-info/dependency_links.txt +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortexpy.egg-info/entry_points.txt +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortexpy.egg-info/requires.txt +0 -0
- {vortexpy-4.1.6 → vortexpy-4.1.7}/vortexpy.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vortexpy"
|
|
7
|
-
version = "4.1.
|
|
7
|
+
version = "4.1.7"
|
|
8
8
|
license = {text = "MIT"}
|
|
9
9
|
authors = [{name = "Flying Ion Pty Ltd", email = "contact@flyingion.com"}]
|
|
10
10
|
description = "Flying Ion's observable, routable, data serialisation and transport code."
|
|
@@ -71,6 +71,7 @@ class PayloadEndpoint:
|
|
|
71
71
|
acceptOnlyFromVortex: Optional[Union[str, tuple]] = None,
|
|
72
72
|
ignoreFromVortex: Optional[Union[str, tuple]] = None,
|
|
73
73
|
requiredPermissions: Optional[PermissionsType] = None,
|
|
74
|
+
logProcessException: bool = True,
|
|
74
75
|
) -> None:
|
|
75
76
|
"""
|
|
76
77
|
:param filt: The filter to match against payloads
|
|
@@ -82,10 +83,13 @@ class PayloadEndpoint:
|
|
|
82
83
|
the caller's session must hold. Pass an empty frozenset to
|
|
83
84
|
explicitly declare "no permissions required". Omitting the
|
|
84
85
|
parameter (``None``) emits a deprecation warning.
|
|
86
|
+
:param logProcessException: When True, handler exceptions log at ERROR.
|
|
87
|
+
When False, the same events log at DEBUG with neutral wording.
|
|
85
88
|
"""
|
|
86
89
|
self._requiredPermissions: Optional[PermissionsType] = (
|
|
87
90
|
requiredPermissions
|
|
88
91
|
)
|
|
92
|
+
self._logProcessException = logProcessException
|
|
89
93
|
|
|
90
94
|
if requiredPermissions is None:
|
|
91
95
|
logger.warning(self._NO_PERMISSIONS_WARNING, filt)
|
|
@@ -115,8 +115,15 @@ class PayloadIO(object):
|
|
|
115
115
|
except Exception as e:
|
|
116
116
|
logger.exception(e)
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
if endpoint._logProcessException:
|
|
119
|
+
vortexLogFailure(failure, logger)
|
|
120
|
+
logger.error(payloadEnvelope.filt)
|
|
121
|
+
else:
|
|
122
|
+
logger.debug(
|
|
123
|
+
"Payload handler response for filt=%s: %s",
|
|
124
|
+
payloadEnvelope.filt,
|
|
125
|
+
failure.getErrorMessage(),
|
|
126
|
+
)
|
|
120
127
|
|
|
121
128
|
def callback(value, blocking=False):
|
|
122
129
|
# A blocking call taking more than 100ms is BAD
|
|
@@ -67,7 +67,7 @@ class PayloadResponse(Deferred):
|
|
|
67
67
|
destVortexUuid: Optional[str] = None,
|
|
68
68
|
timeout: Optional[float] = None,
|
|
69
69
|
resultCheck=True,
|
|
70
|
-
|
|
70
|
+
logTimeoutException=True,
|
|
71
71
|
) -> None:
|
|
72
72
|
"""Constructor
|
|
73
73
|
|
|
@@ -81,6 +81,8 @@ class PayloadResponse(Deferred):
|
|
|
81
81
|
:param timeout The timeout to wait for a response
|
|
82
82
|
:param resultCheck Should the response payload.result be checked, if it fails
|
|
83
83
|
it errback will be called.
|
|
84
|
+
:param logTimeoutException: When True, payload timeouts log at ERROR.
|
|
85
|
+
When False, timeouts log at DEBUG with neutral wording.
|
|
84
86
|
"""
|
|
85
87
|
Deferred.__init__(self)
|
|
86
88
|
|
|
@@ -88,7 +90,7 @@ class PayloadResponse(Deferred):
|
|
|
88
90
|
timeout = self.TIMEOUT
|
|
89
91
|
|
|
90
92
|
self._resultCheck = resultCheck
|
|
91
|
-
self.
|
|
93
|
+
self._logTimeoutException = logTimeoutException
|
|
92
94
|
|
|
93
95
|
# uuid4 can have collisions
|
|
94
96
|
self._messageId = str(uuid4()) + str(PayloadResponse.__SEQ)
|
|
@@ -142,8 +144,10 @@ class PayloadResponse(Deferred):
|
|
|
142
144
|
self._endpoint.shutdown()
|
|
143
145
|
self._endpoint = None
|
|
144
146
|
|
|
145
|
-
if self.
|
|
147
|
+
if self._logTimeoutException:
|
|
146
148
|
logger.error("Timed out for payload %s", self._filt)
|
|
149
|
+
else:
|
|
150
|
+
logger.debug("Payload response timed out for %s", self._filt)
|
|
147
151
|
self._status = self.TIMED_OUT
|
|
148
152
|
return failure
|
|
149
153
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
|
|
4
|
+
from twisted.internet.defer import TimeoutError
|
|
5
|
+
from twisted.python.failure import Failure
|
|
6
|
+
|
|
7
|
+
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
8
|
+
from vortex.PayloadResponse import PayloadResponse
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PayloadResponseLoggingTest(unittest.TestCase):
|
|
12
|
+
@patch("vortex.PayloadResponse.VortexFactory.sendVortexMsg")
|
|
13
|
+
@patch("vortex.PayloadResponse.logger")
|
|
14
|
+
def testTimeoutLogsDebugWhenLogTimeoutExceptionFalse(
|
|
15
|
+
self, mockLogger: MagicMock, _mockSend: MagicMock
|
|
16
|
+
) -> None:
|
|
17
|
+
pr = PayloadResponse(
|
|
18
|
+
PayloadEnvelope(filt={"key": "example"}),
|
|
19
|
+
timeout=30.0,
|
|
20
|
+
logTimeoutException=False,
|
|
21
|
+
)
|
|
22
|
+
pr._timedOut(Failure(TimeoutError("timed out")))
|
|
23
|
+
mockLogger.error.assert_not_called()
|
|
24
|
+
mockLogger.debug.assert_called_once()
|
|
25
|
+
|
|
26
|
+
@patch("vortex.PayloadResponse.VortexFactory.sendVortexMsg")
|
|
27
|
+
@patch("vortex.PayloadResponse.logger")
|
|
28
|
+
def testTimeoutLogsErrorWhenLogTimeoutExceptionTrue(
|
|
29
|
+
self, mockLogger: MagicMock, _mockSend: MagicMock
|
|
30
|
+
) -> None:
|
|
31
|
+
pr = PayloadResponse(
|
|
32
|
+
PayloadEnvelope(filt={"key": "example"}),
|
|
33
|
+
timeout=30.0,
|
|
34
|
+
logTimeoutException=True,
|
|
35
|
+
)
|
|
36
|
+
pr._timedOut(Failure(TimeoutError("timed out")))
|
|
37
|
+
mockLogger.debug.assert_not_called()
|
|
38
|
+
mockLogger.error.assert_called_once()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
unittest.main()
|
|
@@ -167,7 +167,7 @@ class TupleActionProcessorProxy:
|
|
|
167
167
|
payloadEnvelope,
|
|
168
168
|
timeout=PayloadResponse.TIMEOUT - 5, # 5 seconds less
|
|
169
169
|
resultCheck=False,
|
|
170
|
-
|
|
170
|
+
logTimeoutException=False,
|
|
171
171
|
)
|
|
172
172
|
|
|
173
173
|
# This is not a lambda, so that it can have a breakpoint
|
|
@@ -355,7 +355,7 @@ class TupleDataObservableProxyHandler(TupleDataObservableCache):
|
|
|
355
355
|
pr = PayloadResponse(
|
|
356
356
|
payloadEnvelope,
|
|
357
357
|
timeout=PayloadResponse.TIMEOUT - 5, # 5 seconds less
|
|
358
|
-
|
|
358
|
+
logTimeoutException=False,
|
|
359
359
|
)
|
|
360
360
|
|
|
361
361
|
pr.addErrback(self._handlePrFailure, tupleSelector)
|
|
@@ -83,7 +83,7 @@ class TupleDataObserverClient(TupleDataObservableCache):
|
|
|
83
83
|
TupleDataObservableCache.shutdown(self)
|
|
84
84
|
|
|
85
85
|
def pollForTuples(
|
|
86
|
-
self, tupleSelector: TupleSelector,
|
|
86
|
+
self, tupleSelector: TupleSelector, logTimeoutException: bool = True
|
|
87
87
|
) -> Deferred:
|
|
88
88
|
startFilt = copy(self._sendFilt)
|
|
89
89
|
startFilt.update({"subscribe": False, "tupleSelector": tupleSelector})
|
|
@@ -97,7 +97,7 @@ class TupleDataObserverClient(TupleDataObservableCache):
|
|
|
97
97
|
pr = PayloadResponse(
|
|
98
98
|
payloadEnvelope=PayloadEnvelope(startFilt),
|
|
99
99
|
destVortexName=self._destVortexName,
|
|
100
|
-
|
|
100
|
+
logTimeoutException=logTimeoutException,
|
|
101
101
|
)
|
|
102
102
|
pr.addCallback(updateCacheCallback)
|
|
103
103
|
pr.addCallback(
|
|
@@ -21,7 +21,7 @@ from twisted.internet.defer import (
|
|
|
21
21
|
from twisted.internet.threads import deferToThread
|
|
22
22
|
from twisted.python.failure import Failure
|
|
23
23
|
|
|
24
|
-
from vortex.DeferUtil import yesMainThread
|
|
24
|
+
from vortex.DeferUtil import yesMainThread, vortexLogFailure
|
|
25
25
|
from vortex.Payload import Payload
|
|
26
26
|
from vortex.PayloadEndpoint import PayloadEndpoint
|
|
27
27
|
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
@@ -103,7 +103,8 @@ class _VortexRPC:
|
|
|
103
103
|
deferToThread: bool,
|
|
104
104
|
inlineCallbacks: bool,
|
|
105
105
|
requiredPermissions: Optional[PermissionsType] = None,
|
|
106
|
-
|
|
106
|
+
logTimeoutException: bool = True,
|
|
107
|
+
logRpcException: bool = True,
|
|
107
108
|
) -> None:
|
|
108
109
|
"""
|
|
109
110
|
|
|
@@ -125,9 +126,14 @@ class _VortexRPC:
|
|
|
125
126
|
:param inlineCallbacks: Should the function be wrapped in the twisted
|
|
126
127
|
@inlinecallbacks decorator before it's called?.
|
|
127
128
|
|
|
128
|
-
:param
|
|
129
|
-
PayloadResponse
|
|
130
|
-
timeouts (e.g. backend readiness
|
|
129
|
+
:param logTimeoutException: When True, RPC timeouts log at ERROR in
|
|
130
|
+
PayloadResponse. When False, timeouts log at DEBUG with neutral
|
|
131
|
+
wording. Callers that expect timeouts (e.g. backend readiness
|
|
132
|
+
probes) should leave this False.
|
|
133
|
+
|
|
134
|
+
:param logRpcException: When True, RPC handler and caller paths log
|
|
135
|
+
exceptions at ERROR. When False, the same events log at DEBUG
|
|
136
|
+
with neutral wording so log analysers are not triggered.
|
|
131
137
|
|
|
132
138
|
"""
|
|
133
139
|
|
|
@@ -143,7 +149,8 @@ class _VortexRPC:
|
|
|
143
149
|
self.__requiredPermissions: Optional[PermissionsType] = (
|
|
144
150
|
requiredPermissions
|
|
145
151
|
)
|
|
146
|
-
self.
|
|
152
|
+
self.__logTimeoutException = logTimeoutException
|
|
153
|
+
self.__logRpcException = logRpcException
|
|
147
154
|
|
|
148
155
|
self.__funcAcceptsAccessControlContext = (
|
|
149
156
|
checkAccessControlContextSignature(func)
|
|
@@ -182,6 +189,7 @@ class _VortexRPC:
|
|
|
182
189
|
self._filt,
|
|
183
190
|
self._processCall,
|
|
184
191
|
requiredPermissions=self.__requiredPermissions,
|
|
192
|
+
logProcessException=self.__logRpcException,
|
|
185
193
|
)
|
|
186
194
|
logger.debug("RPC %s listening", self.__funcName)
|
|
187
195
|
|
|
@@ -321,7 +329,9 @@ class _VortexRPC:
|
|
|
321
329
|
payloadEnvelope,
|
|
322
330
|
timeout=self.__timeoutSeconds,
|
|
323
331
|
resultCheck=False,
|
|
324
|
-
|
|
332
|
+
logTimeoutException=(
|
|
333
|
+
self.__logRpcException and self.__logTimeoutException
|
|
334
|
+
),
|
|
325
335
|
destVortexName=vortexName,
|
|
326
336
|
destVortexUuid=vortexUuid,
|
|
327
337
|
)
|
|
@@ -346,6 +356,18 @@ class _VortexRPC:
|
|
|
346
356
|
"""
|
|
347
357
|
|
|
348
358
|
if not payloadEnvelope.result in (None, True):
|
|
359
|
+
if self.__logRpcException:
|
|
360
|
+
logger.error(
|
|
361
|
+
"Received RPC failure for %s: %s",
|
|
362
|
+
self.__funcName,
|
|
363
|
+
payloadEnvelope.result,
|
|
364
|
+
)
|
|
365
|
+
else:
|
|
366
|
+
logger.debug(
|
|
367
|
+
"RPC response for %s: %s",
|
|
368
|
+
self.__funcName,
|
|
369
|
+
payloadEnvelope.result,
|
|
370
|
+
)
|
|
349
371
|
return Failure(
|
|
350
372
|
Exception(payloadEnvelope.result).with_traceback(stack),
|
|
351
373
|
exc_tb=stack,
|
|
@@ -372,9 +394,6 @@ class _VortexRPC:
|
|
|
372
394
|
"""
|
|
373
395
|
|
|
374
396
|
if failure.check(TimeoutError):
|
|
375
|
-
if self.__logTimeoutError:
|
|
376
|
-
logger.error("Received RPC timeout for %s", self.__funcName)
|
|
377
|
-
|
|
378
397
|
return Failure(
|
|
379
398
|
Exception(
|
|
380
399
|
"RPC call timed out for %s", self.__funcName
|
|
@@ -382,6 +401,15 @@ class _VortexRPC:
|
|
|
382
401
|
exc_tb=stack,
|
|
383
402
|
)
|
|
384
403
|
|
|
404
|
+
if not failure.check(TimeoutError) and self.__logRpcException:
|
|
405
|
+
vortexLogFailure(failure, logger)
|
|
406
|
+
elif not failure.check(TimeoutError):
|
|
407
|
+
logger.debug(
|
|
408
|
+
"RPC response for %s: %s",
|
|
409
|
+
self.__funcName,
|
|
410
|
+
failure.getErrorMessage(),
|
|
411
|
+
)
|
|
412
|
+
|
|
385
413
|
return failure
|
|
386
414
|
|
|
387
415
|
def callLocally(
|
|
@@ -430,7 +458,8 @@ def vortexRPC(
|
|
|
430
458
|
deferToThread: bool = False,
|
|
431
459
|
inlineCallbacks: bool = False,
|
|
432
460
|
requiredPermissions: Optional[PermissionsType] = None,
|
|
433
|
-
|
|
461
|
+
logTimeoutException: bool = False,
|
|
462
|
+
logRpcException: bool = True,
|
|
434
463
|
):
|
|
435
464
|
"""Vortex RPC Decorator
|
|
436
465
|
|
|
@@ -452,8 +481,12 @@ def vortexRPC(
|
|
|
452
481
|
:param inlineCallbacks: Should the function be wrapped in the twisted
|
|
453
482
|
@inlinecallbacks decorator before it's called?.
|
|
454
483
|
|
|
455
|
-
:param
|
|
456
|
-
|
|
484
|
+
:param logTimeoutException: When True, RPC timeouts log at ERROR.
|
|
485
|
+
When False, timeouts log at DEBUG with neutral wording.
|
|
486
|
+
|
|
487
|
+
:param logRpcException: When True, RPC handler and caller paths log at
|
|
488
|
+
ERROR. When False, the same events log at DEBUG with neutral
|
|
489
|
+
wording.
|
|
457
490
|
|
|
458
491
|
:return A wrapped function, that will now work as an RPC call.
|
|
459
492
|
|
|
@@ -496,7 +529,8 @@ def vortexRPC(
|
|
|
496
529
|
deferToThread,
|
|
497
530
|
inlineCallbacks,
|
|
498
531
|
requiredPermissions=requiredPermissions,
|
|
499
|
-
|
|
532
|
+
logTimeoutException=logTimeoutException,
|
|
533
|
+
logRpcException=logRpcException,
|
|
500
534
|
)
|
|
501
535
|
|
|
502
536
|
return decorator
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from types import SimpleNamespace
|
|
3
|
+
from unittest.mock import ANY, MagicMock, patch
|
|
4
|
+
|
|
5
|
+
from twisted.internet.defer import TimeoutError
|
|
6
|
+
from twisted.python.failure import Failure
|
|
7
|
+
|
|
8
|
+
from vortex.PayloadEndpoint import PayloadEndpoint
|
|
9
|
+
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
10
|
+
from vortex.rpc.RPC import _VortexRPC
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _makeFunc(qualname: str = "testFunc") -> MagicMock:
|
|
14
|
+
func = MagicMock()
|
|
15
|
+
func.__qualname__ = qualname
|
|
16
|
+
func.__globals__ = {"__spec__": SimpleNamespace(name="vortex.rpc.RPC_test")}
|
|
17
|
+
return func
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RpcLoggingTest(unittest.TestCase):
|
|
21
|
+
def setUp(self) -> None:
|
|
22
|
+
_VortexRPC._VortexRPC__registeredFuncNames.clear()
|
|
23
|
+
|
|
24
|
+
def _makeRpc(
|
|
25
|
+
self,
|
|
26
|
+
*,
|
|
27
|
+
logRpcException: bool = True,
|
|
28
|
+
logTimeoutException: bool = False,
|
|
29
|
+
qualname: str = "testFunc",
|
|
30
|
+
) -> _VortexRPC:
|
|
31
|
+
return _VortexRPC(
|
|
32
|
+
_makeFunc(qualname),
|
|
33
|
+
"listenVortexName",
|
|
34
|
+
5.0,
|
|
35
|
+
None,
|
|
36
|
+
{},
|
|
37
|
+
False,
|
|
38
|
+
False,
|
|
39
|
+
logTimeoutException=logTimeoutException,
|
|
40
|
+
logRpcException=logRpcException,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@patch("vortex.rpc.RPC.logger")
|
|
44
|
+
def testRemoteResultLogsDebugWhenLogRpcExceptionFalse(
|
|
45
|
+
self, mockLogger: MagicMock
|
|
46
|
+
) -> None:
|
|
47
|
+
rpc = self._makeRpc(logRpcException=False, qualname="testFuncRemote")
|
|
48
|
+
payloadEnvelope = PayloadEnvelope(filt={}, result="probe not ready")
|
|
49
|
+
|
|
50
|
+
rpc._processResponseCallback(payloadEnvelope, None)
|
|
51
|
+
|
|
52
|
+
mockLogger.error.assert_not_called()
|
|
53
|
+
mockLogger.debug.assert_called_once_with(
|
|
54
|
+
"RPC response for %s: %s",
|
|
55
|
+
rpc._VortexRPC__funcName,
|
|
56
|
+
"probe not ready",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
@patch("vortex.rpc.RPC.logger")
|
|
60
|
+
def testRemoteResultLogsErrorWhenLogRpcExceptionTrue(
|
|
61
|
+
self, mockLogger: MagicMock
|
|
62
|
+
) -> None:
|
|
63
|
+
rpc = self._makeRpc(logRpcException=True, qualname="testFuncRemote2")
|
|
64
|
+
payloadEnvelope = PayloadEnvelope(filt={}, result="probe not ready")
|
|
65
|
+
|
|
66
|
+
rpc._processResponseCallback(payloadEnvelope, None)
|
|
67
|
+
|
|
68
|
+
mockLogger.debug.assert_not_called()
|
|
69
|
+
mockLogger.error.assert_called_once()
|
|
70
|
+
|
|
71
|
+
@patch("vortex.rpc.RPC.logger")
|
|
72
|
+
def testTimeoutErrbackDoesNotLogWhenPayloadResponseAlreadyDid(
|
|
73
|
+
self, mockLogger: MagicMock
|
|
74
|
+
) -> None:
|
|
75
|
+
rpc = self._makeRpc(logRpcException=False, logTimeoutException=True)
|
|
76
|
+
rpc._processResponseErrback(Failure(TimeoutError("timed out")), None)
|
|
77
|
+
mockLogger.error.assert_not_called()
|
|
78
|
+
mockLogger.debug.assert_not_called()
|
|
79
|
+
|
|
80
|
+
@patch("vortex.rpc.RPC.vortexLogFailure")
|
|
81
|
+
@patch("vortex.rpc.RPC.logger")
|
|
82
|
+
def testCallerExceptionLogsDebugWhenLogRpcExceptionFalse(
|
|
83
|
+
self, mockLogger: MagicMock, mockLogFailure: MagicMock
|
|
84
|
+
) -> None:
|
|
85
|
+
rpc = self._makeRpc(logRpcException=False, qualname="testFunc2")
|
|
86
|
+
rpc._processResponseErrback(Failure(ValueError("boom")), None)
|
|
87
|
+
mockLogFailure.assert_not_called()
|
|
88
|
+
mockLogger.error.assert_not_called()
|
|
89
|
+
mockLogger.debug.assert_called_once_with(
|
|
90
|
+
"RPC response for %s: %s",
|
|
91
|
+
rpc._VortexRPC__funcName,
|
|
92
|
+
"boom",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
@patch("vortex.rpc.RPC.vortexLogFailure")
|
|
96
|
+
def testCallerExceptionLoggedWhenLogRpcExceptionTrue(
|
|
97
|
+
self, mockLogFailure: MagicMock
|
|
98
|
+
) -> None:
|
|
99
|
+
rpc = self._makeRpc(logRpcException=True, qualname="testFunc3")
|
|
100
|
+
failure = Failure(ValueError("boom"))
|
|
101
|
+
rpc._processResponseErrback(failure, None)
|
|
102
|
+
mockLogFailure.assert_called_once_with(failure, ANY)
|
|
103
|
+
|
|
104
|
+
def testPayloadEndpointStoresLogProcessException(self) -> None:
|
|
105
|
+
endpoint = PayloadEndpoint(
|
|
106
|
+
dict(key="example"),
|
|
107
|
+
lambda **kwargs: None,
|
|
108
|
+
logProcessException=False,
|
|
109
|
+
)
|
|
110
|
+
self.assertFalse(endpoint._logProcessException)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
unittest.main()
|
|
@@ -12,6 +12,7 @@ vortex/PayloadIO.py
|
|
|
12
12
|
vortex/PayloadPriority.py
|
|
13
13
|
vortex/PayloadResponse.py
|
|
14
14
|
vortex/PayloadResponseTest.py
|
|
15
|
+
vortex/PayloadResponse_test.py
|
|
15
16
|
vortex/PayloadTest.py
|
|
16
17
|
vortex/PerformTestActionTuple.py
|
|
17
18
|
vortex/SerialiseUtil.py
|
|
@@ -86,6 +87,7 @@ vortex/restful/RestfulResourceTest.py
|
|
|
86
87
|
vortex/restful/__init__.py
|
|
87
88
|
vortex/rpc/RPC.py
|
|
88
89
|
vortex/rpc/RPCTest.py
|
|
90
|
+
vortex/rpc/RPC_test.py
|
|
89
91
|
vortex/rpc/__init__.py
|
|
90
92
|
vortex/sqla_orm/OrmCrudHandler.py
|
|
91
93
|
vortex/sqla_orm/TupleProviderForDb.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|