vortexpy 4.3.1__tar.gz → 4.3.4__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.3.1 → vortexpy-4.3.4}/PKG-INFO +1 -1
- {vortexpy-4.3.1 → vortexpy-4.3.4}/pyproject.toml +1 -1
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexSession.py +1 -1
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/__init__.py +1 -1
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/handler/TupleActionProcessor.py +4 -1
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/handler/TupleDataAccessGranterABC.py +4 -4
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/handler/TupleDataObservableHandler.py +57 -49
- vortexpy-4.3.4/vortex/handler/TupleDataObservableHandler_test.py +264 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortexpy.egg-info/PKG-INFO +1 -1
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortexpy.egg-info/SOURCES.txt +1 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/README.md +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/setup.cfg +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/DataWrapTuple.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/DeferUtil.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/DeferUtil_test.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/Jsonable.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/MtlsAuthorizedClientRegistry.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/MtlsAuthorizedClientRegistry_test.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/MtlsServerSslOptions.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/Payload.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/PayloadEndpoint.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/PayloadEndpointTest.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/PayloadEnvelope.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/PayloadFilterKeys.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/PayloadIO.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/PayloadPriority.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/PayloadResponse.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/PayloadResponseTest.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/PayloadResponse_test.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/PayloadTest.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/PerformTestActionTuple.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/SerialiseUtil.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/SerialiseUtil_test.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/TestTuple.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/Tuple.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/TupleAction.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/TupleBson.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/TupleBson_test.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/TupleDataForTest.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/TupleDocumentConverter.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/TuplePerfTest.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/TuplePolymorphicTupleTypeFieldArgTest.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/TupleRestful.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/TupleRestful_test.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/TupleSelector.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/TupleTest.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexABC.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexAccessControlContextABC.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexAuthenticatorABC.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexClientHttp.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexClientHttpTest.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexClientTcp.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexClientWebsocketFactory.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexConnectionABC.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexEndpointPermission.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexFactory.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexFactory_test.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexPayloadProtocol.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexPermission.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexPermissionError.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexServer.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexServerConnection.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexServerConnection_test.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexServerHttpResource.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexServerTcp.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexServerWebsocket.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexSession_test.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexTcpMemoryLeakPayloadTest.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexTcpMemoryLeakRawTest.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexTest.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexUtil.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/VortexWritePushProducer.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/data_loader/TupleDataLiveLockManager.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/data_loader/TupleDataLoader.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/data_loader/TupleDataLoaderDelegate.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/data_loader/TupleDataLoaderTupleABC.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/data_loader/TupleDataLoaderTuples.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/data_loader/__init__.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/event_bus/TupleChangeEventABC.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/event_bus/TupleChangeEventBus.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/event_bus/TupleChangeEventBusObserverABC.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/event_bus/__init__.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/handler/TupleActionProcessorProxy.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/handler/TupleActionProcessorTestHandler.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/handler/TupleActionProcessor_shutdown_test.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/handler/TupleActionPushOffline.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/handler/TupleActionPushOffline_test.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/handler/TupleDataActionClient.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/handler/TupleDataObservableCache.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/handler/TupleDataObservableProxyHandler.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/handler/TupleDataObservableTestHandler.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/handler/TupleDataObserverClient.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/handler/TupleDataProviderABC.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/handler/VortexJSTupleLoaderTestHandler.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/handler/__init__.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/mypy_plugin.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/payload_many_tuples_benchmark_test.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/py.typed +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/restful/GzippedDataHttpClient.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/restful/GzippedDataHttpClientTest.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/restful/GzippedPayloadHttpClient.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/restful/RestfulHttpClient.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/restful/RestfulResource.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/restful/__init__.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/restful/restful_http_client_test.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/restful/restful_resource_test.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/rpc/RPC.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/rpc/RPCTest.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/rpc/RPC_test.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/rpc/__init__.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/sqla_orm/OrmCrudHandler.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/sqla_orm/TupleProviderForDb.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/sqla_orm/VortexTupleJson.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/sqla_orm/__init__.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/storage/TupleActionStorageSqlite.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/storage/TupleStorageSqlite.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/storage/__init__.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/tuple_json_schema/__init__.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/tuple_json_schema/constants.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/tuple_json_schema/json_schema_node_tuple.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/tuple_json_schema/tuple_json_schema_builder.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/tuple_json_schema/tuple_json_schema_test.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/tuple_json_schema/tuple_json_schema_test_tuples.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/tuple_test_fixtures.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/twisted_runtime_metrics.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortex/twisted_runtime_metrics_test.py +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortexpy.egg-info/dependency_links.txt +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortexpy.egg-info/entry_points.txt +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/vortexpy.egg-info/requires.txt +0 -0
- {vortexpy-4.3.1 → vortexpy-4.3.4}/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.3.
|
|
7
|
+
version = "4.3.4"
|
|
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."
|
|
@@ -188,7 +188,10 @@ class TupleActionProcessor:
|
|
|
188
188
|
return tupleName in self._tupleProcessorsByTupleName
|
|
189
189
|
|
|
190
190
|
def shutdown(self):
|
|
191
|
-
self
|
|
191
|
+
endpoint = getattr(self, "_endpoint", None)
|
|
192
|
+
if endpoint is not None:
|
|
193
|
+
endpoint.shutdown()
|
|
194
|
+
self._endpoint = None
|
|
192
195
|
|
|
193
196
|
def _denyDelegate(
|
|
194
197
|
self, log_fn, log_msg, error_msg, filt, tupleName, sendResponse
|
|
@@ -17,16 +17,16 @@ from vortex.VortexAccessControlContextABC import VortexAccessControlContextABC
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class TupleDataAccessGranterABC(ABC):
|
|
20
|
-
"""Abstract base class for per-observer Level 2
|
|
20
|
+
"""Abstract base class for per-observer Level 2 tuple data access control.
|
|
21
21
|
|
|
22
22
|
Register a concrete implementation via
|
|
23
23
|
:meth:`~vortex.handler.TupleDataObservableHandler.TupleDataObservableHandler.addTupleProvider`
|
|
24
24
|
(``accessGranter=`` parameter) or via
|
|
25
25
|
:meth:`~vortex.handler.TupleDataObservableProxyHandler.TupleDataObservableProxyHandler.addTupleAccessGranter`.
|
|
26
26
|
|
|
27
|
-
Called
|
|
28
|
-
|
|
29
|
-
``False`` to
|
|
27
|
+
Called before a client subscribes (initial response) and for each subscribed
|
|
28
|
+
observer before a server-push update is sent. Return ``True`` (or a Deferred
|
|
29
|
+
that fires with ``True``) to allow delivery, ``False`` to deny that observer.
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
32
|
@abstractmethod
|
|
@@ -28,14 +28,12 @@ from vortex.PayloadEndpoint import PayloadEndpoint
|
|
|
28
28
|
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
29
29
|
from vortex.TupleSelector import TupleSelector
|
|
30
30
|
from vortex.VortexABC import SendVortexMsgResponseCallable
|
|
31
|
+
from vortex.VortexAccessControlContextABC import NO_AUTH_ACCESS_CONTROL_CONTEXT
|
|
31
32
|
from vortex.VortexFactory import VortexFactory
|
|
32
33
|
from twisted.internet.defer import maybeDeferred as _maybeDeferred
|
|
33
34
|
|
|
34
35
|
from vortex.handler.TupleDataProviderABC import TupleDataProviderABC
|
|
35
36
|
from vortex.handler.TupleDataAccessGranterABC import TupleDataAccessGranterABC
|
|
36
|
-
from vortex.VortexEndpointPermission import (
|
|
37
|
-
EndpointPermissionsType as PermissionsType,
|
|
38
|
-
)
|
|
39
37
|
from vortex.VortexSession import checkNoHttpSessionInSignature
|
|
40
38
|
from vortex.VortexUtil import limitConcurrency, _dedupProcessorCallKeys
|
|
41
39
|
|
|
@@ -65,9 +63,7 @@ class TupleSelectorUpdateMapperABC(metaclass=ABCMeta):
|
|
|
65
63
|
"""
|
|
66
64
|
|
|
67
65
|
|
|
68
|
-
_ObserverDetails = namedtuple(
|
|
69
|
-
"_ObserverDetails", ["vortexUuid", "observerName"]
|
|
70
|
-
)
|
|
66
|
+
_ObserverDetails = namedtuple("_ObserverDetails", ["vortexUuid", "observerName"])
|
|
71
67
|
|
|
72
68
|
|
|
73
69
|
class _ObserverData:
|
|
@@ -114,9 +110,7 @@ class TupleDataObservableHandler:
|
|
|
114
110
|
self._observerDataByTupleSelector = {}
|
|
115
111
|
|
|
116
112
|
self._tupleProvidersByTupleName = {}
|
|
117
|
-
self._accessGrantersByTupleName: Dict[
|
|
118
|
-
str, TupleDataAccessGranterABC
|
|
119
|
-
] = {}
|
|
113
|
+
self._accessGrantersByTupleName: Dict[str, TupleDataAccessGranterABC] = {}
|
|
120
114
|
self._tupleSelectorUpdateMappers = []
|
|
121
115
|
|
|
122
116
|
def addTupleProvider(
|
|
@@ -126,7 +120,7 @@ class TupleDataObservableHandler:
|
|
|
126
120
|
accessGranter: Optional[TupleDataAccessGranterABC] = None,
|
|
127
121
|
):
|
|
128
122
|
"""Add Tuple Provider"""
|
|
129
|
-
assert not
|
|
123
|
+
assert tupleName not in self._tupleProvidersByTupleName, (
|
|
130
124
|
"Observable:%s, Tuple name %s is already registered"
|
|
131
125
|
% (
|
|
132
126
|
self._observableName,
|
|
@@ -150,16 +144,13 @@ class TupleDataObservableHandler:
|
|
|
150
144
|
self._accessGrantersByTupleName[tupleName] = accessGranter
|
|
151
145
|
|
|
152
146
|
providerDesc = (
|
|
153
|
-
f"{type(provider).__module__}."
|
|
154
|
-
f"{type(provider).__qualname__}.makeVortexMsg"
|
|
147
|
+
f"{type(provider).__module__}.{type(provider).__qualname__}.makeVortexMsg"
|
|
155
148
|
)
|
|
156
149
|
checkNoHttpSessionInSignature(provider.makeVortexMsg, providerDesc)
|
|
157
150
|
|
|
158
151
|
self._tupleProvidersByTupleName[tupleName] = provider
|
|
159
152
|
|
|
160
|
-
def addTupleSelectorUpdateMapper(
|
|
161
|
-
self, mapper: TupleSelectorUpdateMapperABC
|
|
162
|
-
):
|
|
153
|
+
def addTupleSelectorUpdateMapper(self, mapper: TupleSelectorUpdateMapperABC):
|
|
163
154
|
"""Add Tuple Selector Update Mapper
|
|
164
155
|
|
|
165
156
|
This mapper will be called every time a tuple selector is notified of an update.
|
|
@@ -180,9 +171,7 @@ class TupleDataObservableHandler:
|
|
|
180
171
|
for jsonStr in self._observerDataByTupleSelector
|
|
181
172
|
]
|
|
182
173
|
|
|
183
|
-
def tupleSubscriberCount(
|
|
184
|
-
self, tupleNames: Optional[frozenset[str]] = None
|
|
185
|
-
) -> int:
|
|
174
|
+
def tupleSubscriberCount(self, tupleNames: Optional[frozenset[str]] = None) -> int:
|
|
186
175
|
subscriberVortexUuids: set[str] = set()
|
|
187
176
|
for observerData in self._observerDataByTupleSelector.values():
|
|
188
177
|
if (
|
|
@@ -255,9 +244,7 @@ class TupleDataObservableHandler:
|
|
|
255
244
|
if d:
|
|
256
245
|
d.addErrback(vortexLogFailure, logger, consumeError=True)
|
|
257
246
|
|
|
258
|
-
@limitConcurrency(
|
|
259
|
-
logger, 25, deduplicateBasedOnKwArgsKey=_dedupProcessorCallKeys
|
|
260
|
-
)
|
|
247
|
+
@limitConcurrency(logger, 25, deduplicateBasedOnKwArgsKey=_dedupProcessorCallKeys)
|
|
261
248
|
def _processLimitedDecorated(
|
|
262
249
|
self,
|
|
263
250
|
payloadEnvelope: PayloadEnvelope,
|
|
@@ -286,9 +273,7 @@ class TupleDataObservableHandler:
|
|
|
286
273
|
_ = vortexName
|
|
287
274
|
|
|
288
275
|
if not VortexFactory.isVortexUuidOnline(vortexUuid):
|
|
289
|
-
logger.debug(
|
|
290
|
-
"Vortex %s is no longer online, skipping reply", vortexUuid
|
|
291
|
-
)
|
|
276
|
+
logger.debug("Vortex %s is no longer online, skipping reply", vortexUuid)
|
|
292
277
|
return
|
|
293
278
|
|
|
294
279
|
# logger.debug(
|
|
@@ -319,12 +304,8 @@ class TupleDataObservableHandler:
|
|
|
319
304
|
tupleSelector.name,
|
|
320
305
|
)
|
|
321
306
|
return
|
|
322
|
-
endpointPermissions = yield _maybeDeferred(
|
|
323
|
-
|
|
324
|
-
)
|
|
325
|
-
if not tupleProvider.requiredPermissions.issubset(
|
|
326
|
-
endpointPermissions
|
|
327
|
-
):
|
|
307
|
+
endpointPermissions = yield _maybeDeferred(auth.getEndpointPermissions, vs)
|
|
308
|
+
if not tupleProvider.requiredPermissions.issubset(endpointPermissions):
|
|
328
309
|
logger.warning(
|
|
329
310
|
"Observable:%s permission denied for %s",
|
|
330
311
|
self._observableName,
|
|
@@ -360,18 +341,50 @@ class TupleDataObservableHandler:
|
|
|
360
341
|
|
|
361
342
|
return
|
|
362
343
|
|
|
344
|
+
granter = self._accessGrantersByTupleName.get(tupleSelector.name)
|
|
345
|
+
if granter:
|
|
346
|
+
filt = dict(tupleSelector=tupleSelector)
|
|
347
|
+
filt.update(self._filt)
|
|
348
|
+
auth = VortexFactory.getAuthenticator()
|
|
349
|
+
ctx = NO_AUTH_ACCESS_CONTROL_CONTEXT
|
|
350
|
+
if auth:
|
|
351
|
+
if not vs:
|
|
352
|
+
logger.warning(
|
|
353
|
+
"Observable:%s access denied for %s:"
|
|
354
|
+
" no VortexSession for access control context.",
|
|
355
|
+
self._observableName,
|
|
356
|
+
tupleSelector.name,
|
|
357
|
+
)
|
|
358
|
+
return
|
|
359
|
+
ctx = yield _maybeDeferred(
|
|
360
|
+
auth.getAccessControlContext,
|
|
361
|
+
vs,
|
|
362
|
+
vs.vortexName,
|
|
363
|
+
vortexUuid,
|
|
364
|
+
)
|
|
365
|
+
if ctx is None:
|
|
366
|
+
logger.warning(
|
|
367
|
+
"Observable:%s access denied for %s:"
|
|
368
|
+
" access control context unavailable.",
|
|
369
|
+
self._observableName,
|
|
370
|
+
tupleSelector.name,
|
|
371
|
+
)
|
|
372
|
+
return
|
|
373
|
+
granted = yield granter.isAccessGranted(filt, tupleSelector, ctx)
|
|
374
|
+
if not granted:
|
|
375
|
+
logger.warning(
|
|
376
|
+
"Observable:%s access denied for %s",
|
|
377
|
+
self._observableName,
|
|
378
|
+
tupleSelector.name,
|
|
379
|
+
)
|
|
380
|
+
return
|
|
381
|
+
|
|
363
382
|
# Add support for just getting data, no subscription.
|
|
364
|
-
if self._subscriptionsEnabled and payloadEnvelope.filt.get(
|
|
365
|
-
"subscribe", True
|
|
366
|
-
):
|
|
383
|
+
if self._subscriptionsEnabled and payloadEnvelope.filt.get("subscribe", True):
|
|
367
384
|
if tsStr not in self._observerDataByTupleSelector:
|
|
368
|
-
self._observerDataByTupleSelector[tsStr] = _ObserverData(
|
|
369
|
-
tupleSelector
|
|
370
|
-
)
|
|
385
|
+
self._observerDataByTupleSelector[tsStr] = _ObserverData(tupleSelector)
|
|
371
386
|
|
|
372
|
-
self._observerDataByTupleSelector[tsStr].observers.add(
|
|
373
|
-
observerDetails
|
|
374
|
-
)
|
|
387
|
+
self._observerDataByTupleSelector[tsStr].observers.add(observerDetails)
|
|
375
388
|
self._observerDataByTupleSelector[tsStr].vortexSessionByVortexUuid[
|
|
376
389
|
vortexUuid
|
|
377
390
|
] = vs
|
|
@@ -395,8 +408,7 @@ class TupleDataObservableHandler:
|
|
|
395
408
|
) -> List[TupleSelector]:
|
|
396
409
|
# Get all tuple selectors
|
|
397
410
|
allTupleSelectors = [
|
|
398
|
-
data.tupleSelector
|
|
399
|
-
for data in self._observerDataByTupleSelector.values()
|
|
411
|
+
data.tupleSelector for data in self._observerDataByTupleSelector.values()
|
|
400
412
|
]
|
|
401
413
|
|
|
402
414
|
# Create a dict so we end up with only unique ones
|
|
@@ -404,9 +416,7 @@ class TupleDataObservableHandler:
|
|
|
404
416
|
|
|
405
417
|
# Run through the mappers
|
|
406
418
|
for mapper in self._tupleSelectorUpdateMappers:
|
|
407
|
-
mappedSelectors = mapper.mapTupleSelector(
|
|
408
|
-
tupleSelector, allTupleSelectors
|
|
409
|
-
)
|
|
419
|
+
mappedSelectors = mapper.mapTupleSelector(tupleSelector, allTupleSelectors)
|
|
410
420
|
if mappedSelectors:
|
|
411
421
|
tupleSelectorByStr.update(
|
|
412
422
|
{ts.toJsonStr(): ts for ts in mappedSelectors}
|
|
@@ -498,9 +508,7 @@ class TupleDataObservableHandler:
|
|
|
498
508
|
# Send the vortex messages, checking access per observer if a granter is set
|
|
499
509
|
for destVortexUuid in destVortexUuids:
|
|
500
510
|
if granter:
|
|
501
|
-
sessionVs = observerData.vortexSessionByVortexUuid.get(
|
|
502
|
-
destVortexUuid
|
|
503
|
-
)
|
|
511
|
+
sessionVs = observerData.vortexSessionByVortexUuid.get(destVortexUuid)
|
|
504
512
|
ctx = None
|
|
505
513
|
if auth and sessionVs:
|
|
506
514
|
ctx = yield _maybeDeferred(
|
|
@@ -509,8 +517,8 @@ class TupleDataObservableHandler:
|
|
|
509
517
|
sessionVs.vortexName,
|
|
510
518
|
destVortexUuid,
|
|
511
519
|
)
|
|
512
|
-
granted = yield
|
|
513
|
-
|
|
520
|
+
granted = yield granter.isAccessGranted(
|
|
521
|
+
filt, tupleSelector, ctx
|
|
514
522
|
)
|
|
515
523
|
if not granted:
|
|
516
524
|
continue
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# ruff: noqa: N999
|
|
2
|
+
# Copyright (c) 2016-2026 Flying Ion Pty Ltd
|
|
3
|
+
#
|
|
4
|
+
# This software is open source; the MIT license applies.
|
|
5
|
+
# See LICENSE for the full license text.
|
|
6
|
+
#
|
|
7
|
+
# Website : https://flyingion.com
|
|
8
|
+
# Support : contact@flyingion.com
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
from unittest.mock import MagicMock, patch
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
from pytest_twisted import ensureDeferred # type: ignore[import-untyped]
|
|
17
|
+
from twisted.internet.defer import Deferred, succeed
|
|
18
|
+
|
|
19
|
+
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
20
|
+
from vortex.TupleSelector import TupleSelector
|
|
21
|
+
from vortex.VortexAccessControlContextABC import VortexAccessControlContextABC
|
|
22
|
+
from vortex.VortexSession import VortexSession
|
|
23
|
+
from vortex.handler.TupleDataAccessGranterABC import TupleDataAccessGranterABC
|
|
24
|
+
from vortex.handler.TupleDataObservableHandler import TupleDataObservableHandler
|
|
25
|
+
from vortex.handler.TupleDataProviderABC import TupleDataProviderABC
|
|
26
|
+
|
|
27
|
+
_TUPLE_NAME = "testTuples"
|
|
28
|
+
_TUPLE_SELECTOR = TupleSelector(_TUPLE_NAME, {"count": 2})
|
|
29
|
+
_VORTEX_UUID = "client-vortex-uuid-1"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _TestAccessControlContext(VortexAccessControlContextABC):
|
|
33
|
+
def __init__(self) -> None:
|
|
34
|
+
super().__init__(
|
|
35
|
+
vortexName="client-vortex",
|
|
36
|
+
vortexUuid=_VORTEX_UUID,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _RecordingGranter(TupleDataAccessGranterABC):
|
|
41
|
+
def __init__(self, granted: bool) -> None:
|
|
42
|
+
self.granted = granted
|
|
43
|
+
self.calls: list[tuple[dict, TupleSelector, Any]] = []
|
|
44
|
+
|
|
45
|
+
def isAccessGranted(
|
|
46
|
+
self,
|
|
47
|
+
filt: dict,
|
|
48
|
+
tupleSelector: TupleSelector,
|
|
49
|
+
accessControlContext: VortexAccessControlContextABC,
|
|
50
|
+
) -> bool:
|
|
51
|
+
self.calls.append((filt, tupleSelector, accessControlContext))
|
|
52
|
+
return self.granted
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class _StubProvider(TupleDataProviderABC):
|
|
56
|
+
requiredPermissions = frozenset()
|
|
57
|
+
|
|
58
|
+
def __init__(self) -> None:
|
|
59
|
+
self.makeVortexMsgCalls = 0
|
|
60
|
+
|
|
61
|
+
def makeVortexMsg(
|
|
62
|
+
self,
|
|
63
|
+
filt: dict,
|
|
64
|
+
tupleSelector: TupleSelector,
|
|
65
|
+
) -> bytes:
|
|
66
|
+
self.makeVortexMsgCalls += 1
|
|
67
|
+
return b"stub-vortex-msg"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _makeHandler(
|
|
71
|
+
granter: TupleDataAccessGranterABC | None = None,
|
|
72
|
+
) -> tuple[TupleDataObservableHandler, _StubProvider]:
|
|
73
|
+
handler = TupleDataObservableHandler("testObservable")
|
|
74
|
+
provider = _StubProvider()
|
|
75
|
+
handler.addTupleProvider(_TUPLE_NAME, provider, accessGranter=granter)
|
|
76
|
+
return handler, provider
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _subscribeEnvelope() -> PayloadEnvelope:
|
|
80
|
+
return PayloadEnvelope(
|
|
81
|
+
filt=dict(
|
|
82
|
+
tupleSelector=_TUPLE_SELECTOR,
|
|
83
|
+
subscribe=True,
|
|
84
|
+
observerName="testObserver",
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@pytest.mark.twisted
|
|
90
|
+
@ensureDeferred
|
|
91
|
+
async def test_subscribe_granter_grants_registers_observer_and_responds() -> None:
|
|
92
|
+
granter = _RecordingGranter(granted=True)
|
|
93
|
+
handler, provider = _makeHandler(granter)
|
|
94
|
+
vortexSession = VortexSession(ip="127.0.0.1", vortexName="client-vortex")
|
|
95
|
+
accessControlContext = _TestAccessControlContext()
|
|
96
|
+
auth = MagicMock()
|
|
97
|
+
auth.getAccessControlContext.return_value = succeed(accessControlContext)
|
|
98
|
+
sendResponseCalls: list[bytes] = []
|
|
99
|
+
|
|
100
|
+
def sendResponse(vortexMsg: bytes) -> Deferred[None]:
|
|
101
|
+
sendResponseCalls.append(vortexMsg)
|
|
102
|
+
return succeed(None)
|
|
103
|
+
|
|
104
|
+
with (
|
|
105
|
+
patch(
|
|
106
|
+
"vortex.handler.TupleDataObservableHandler.VortexFactory.isVortexUuidOnline",
|
|
107
|
+
return_value=True,
|
|
108
|
+
),
|
|
109
|
+
patch(
|
|
110
|
+
"vortex.handler.TupleDataObservableHandler.VortexFactory.getAuthenticator",
|
|
111
|
+
return_value=auth,
|
|
112
|
+
),
|
|
113
|
+
):
|
|
114
|
+
await handler._process(
|
|
115
|
+
_subscribeEnvelope(),
|
|
116
|
+
_VORTEX_UUID,
|
|
117
|
+
"client-vortex",
|
|
118
|
+
sendResponse,
|
|
119
|
+
vortexSession=vortexSession,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
assert len(granter.calls) == 1, "granter should run once on subscribe"
|
|
123
|
+
assert granter.calls[0][2] is accessControlContext, (
|
|
124
|
+
"granter should receive resolved access control context"
|
|
125
|
+
)
|
|
126
|
+
assert handler.hasTupleSubscribers(_TUPLE_SELECTOR) is True, (
|
|
127
|
+
"granted subscribe should register observer"
|
|
128
|
+
)
|
|
129
|
+
assert provider.makeVortexMsgCalls == 1, "granted subscribe should fetch tuple data"
|
|
130
|
+
assert len(sendResponseCalls) == 1, "granted subscribe should send initial response"
|
|
131
|
+
auth.getAccessControlContext.assert_called_once_with(
|
|
132
|
+
vortexSession,
|
|
133
|
+
vortexSession.vortexName,
|
|
134
|
+
_VORTEX_UUID,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@pytest.mark.twisted
|
|
139
|
+
@ensureDeferred
|
|
140
|
+
async def test_subscribe_granter_denies_without_observer_or_response() -> None:
|
|
141
|
+
granter = _RecordingGranter(granted=False)
|
|
142
|
+
handler, provider = _makeHandler(granter)
|
|
143
|
+
vortexSession = VortexSession(ip="127.0.0.1", vortexName="client-vortex")
|
|
144
|
+
auth = MagicMock()
|
|
145
|
+
auth.getAccessControlContext.return_value = succeed(_TestAccessControlContext())
|
|
146
|
+
sendResponseCalls: list[bytes] = []
|
|
147
|
+
|
|
148
|
+
def sendResponse(vortexMsg: bytes) -> Deferred[None]:
|
|
149
|
+
sendResponseCalls.append(vortexMsg)
|
|
150
|
+
return succeed(None)
|
|
151
|
+
|
|
152
|
+
with (
|
|
153
|
+
patch(
|
|
154
|
+
"vortex.handler.TupleDataObservableHandler.VortexFactory.isVortexUuidOnline",
|
|
155
|
+
return_value=True,
|
|
156
|
+
),
|
|
157
|
+
patch(
|
|
158
|
+
"vortex.handler.TupleDataObservableHandler.VortexFactory.getAuthenticator",
|
|
159
|
+
return_value=auth,
|
|
160
|
+
),
|
|
161
|
+
):
|
|
162
|
+
await handler._process(
|
|
163
|
+
_subscribeEnvelope(),
|
|
164
|
+
_VORTEX_UUID,
|
|
165
|
+
"client-vortex",
|
|
166
|
+
sendResponse,
|
|
167
|
+
vortexSession=vortexSession,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
assert len(granter.calls) == 1, "denied subscribe should still consult granter"
|
|
171
|
+
assert handler.hasTupleSubscribers(_TUPLE_SELECTOR) is False, (
|
|
172
|
+
"denied subscribe must not register observer"
|
|
173
|
+
)
|
|
174
|
+
assert provider.makeVortexMsgCalls == 0, (
|
|
175
|
+
"denied subscribe must not call makeVortexMsg"
|
|
176
|
+
)
|
|
177
|
+
assert sendResponseCalls == [], "denied subscribe must not send response"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@pytest.mark.twisted
|
|
181
|
+
@ensureDeferred
|
|
182
|
+
async def test_subscribe_granter_denies_when_auth_required_without_session() -> None:
|
|
183
|
+
granter = _RecordingGranter(granted=True)
|
|
184
|
+
handler, provider = _makeHandler(granter)
|
|
185
|
+
auth = MagicMock()
|
|
186
|
+
sendResponseCalls: list[bytes] = []
|
|
187
|
+
|
|
188
|
+
def sendResponse(vortexMsg: bytes) -> Deferred[None]:
|
|
189
|
+
sendResponseCalls.append(vortexMsg)
|
|
190
|
+
return succeed(None)
|
|
191
|
+
|
|
192
|
+
with (
|
|
193
|
+
patch(
|
|
194
|
+
"vortex.handler.TupleDataObservableHandler.VortexFactory.isVortexUuidOnline",
|
|
195
|
+
return_value=True,
|
|
196
|
+
),
|
|
197
|
+
patch(
|
|
198
|
+
"vortex.handler.TupleDataObservableHandler.VortexFactory.getAuthenticator",
|
|
199
|
+
return_value=auth,
|
|
200
|
+
),
|
|
201
|
+
):
|
|
202
|
+
await handler._process(
|
|
203
|
+
_subscribeEnvelope(),
|
|
204
|
+
_VORTEX_UUID,
|
|
205
|
+
"client-vortex",
|
|
206
|
+
sendResponse,
|
|
207
|
+
vortexSession=None,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
assert granter.calls == [], (
|
|
211
|
+
"missing session with auth registered must deny before granter"
|
|
212
|
+
)
|
|
213
|
+
auth.getAccessControlContext.assert_not_called()
|
|
214
|
+
assert handler.hasTupleSubscribers(_TUPLE_SELECTOR) is False, (
|
|
215
|
+
"missing session must not register observer"
|
|
216
|
+
)
|
|
217
|
+
assert provider.makeVortexMsgCalls == 0, (
|
|
218
|
+
"missing session must not call makeVortexMsg"
|
|
219
|
+
)
|
|
220
|
+
assert sendResponseCalls == [], "missing session must not send response"
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@pytest.mark.twisted
|
|
224
|
+
@ensureDeferred
|
|
225
|
+
async def test_subscribe_granter_denies_when_context_unavailable() -> None:
|
|
226
|
+
granter = _RecordingGranter(granted=True)
|
|
227
|
+
handler, provider = _makeHandler(granter)
|
|
228
|
+
vortexSession = VortexSession(ip="127.0.0.1", vortexName="client-vortex")
|
|
229
|
+
auth = MagicMock()
|
|
230
|
+
auth.getAccessControlContext.return_value = succeed(None)
|
|
231
|
+
sendResponseCalls: list[bytes] = []
|
|
232
|
+
|
|
233
|
+
def sendResponse(vortexMsg: bytes) -> Deferred[None]:
|
|
234
|
+
sendResponseCalls.append(vortexMsg)
|
|
235
|
+
return succeed(None)
|
|
236
|
+
|
|
237
|
+
with (
|
|
238
|
+
patch(
|
|
239
|
+
"vortex.handler.TupleDataObservableHandler.VortexFactory.isVortexUuidOnline",
|
|
240
|
+
return_value=True,
|
|
241
|
+
),
|
|
242
|
+
patch(
|
|
243
|
+
"vortex.handler.TupleDataObservableHandler.VortexFactory.getAuthenticator",
|
|
244
|
+
return_value=auth,
|
|
245
|
+
),
|
|
246
|
+
):
|
|
247
|
+
await handler._process(
|
|
248
|
+
_subscribeEnvelope(),
|
|
249
|
+
_VORTEX_UUID,
|
|
250
|
+
"client-vortex",
|
|
251
|
+
sendResponse,
|
|
252
|
+
vortexSession=vortexSession,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
assert granter.calls == [], (
|
|
256
|
+
"unavailable access control context must deny before granter"
|
|
257
|
+
)
|
|
258
|
+
assert handler.hasTupleSubscribers(_TUPLE_SELECTOR) is False, (
|
|
259
|
+
"unavailable context must not register observer"
|
|
260
|
+
)
|
|
261
|
+
assert provider.makeVortexMsgCalls == 0, (
|
|
262
|
+
"unavailable context must not call makeVortexMsg"
|
|
263
|
+
)
|
|
264
|
+
assert sendResponseCalls == [], "unavailable context must not send response"
|
|
@@ -88,6 +88,7 @@ vortex/handler/TupleDataAccessGranterABC.py
|
|
|
88
88
|
vortex/handler/TupleDataActionClient.py
|
|
89
89
|
vortex/handler/TupleDataObservableCache.py
|
|
90
90
|
vortex/handler/TupleDataObservableHandler.py
|
|
91
|
+
vortex/handler/TupleDataObservableHandler_test.py
|
|
91
92
|
vortex/handler/TupleDataObservableProxyHandler.py
|
|
92
93
|
vortex/handler/TupleDataObservableTestHandler.py
|
|
93
94
|
vortex/handler/TupleDataObserverClient.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
|
|
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
|