vortexpy 4.3.3__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.
Files changed (130) hide show
  1. {vortexpy-4.3.3 → vortexpy-4.3.4}/PKG-INFO +1 -1
  2. {vortexpy-4.3.3 → vortexpy-4.3.4}/pyproject.toml +1 -1
  3. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/__init__.py +1 -1
  4. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/handler/TupleActionProcessor.py +4 -1
  5. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/handler/TupleDataAccessGranterABC.py +4 -4
  6. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/handler/TupleDataObservableHandler.py +57 -49
  7. vortexpy-4.3.4/vortex/handler/TupleDataObservableHandler_test.py +264 -0
  8. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortexpy.egg-info/PKG-INFO +1 -1
  9. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortexpy.egg-info/SOURCES.txt +1 -0
  10. {vortexpy-4.3.3 → vortexpy-4.3.4}/README.md +0 -0
  11. {vortexpy-4.3.3 → vortexpy-4.3.4}/setup.cfg +0 -0
  12. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/DataWrapTuple.py +0 -0
  13. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/DeferUtil.py +0 -0
  14. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/DeferUtil_test.py +0 -0
  15. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/Jsonable.py +0 -0
  16. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/MtlsAuthorizedClientRegistry.py +0 -0
  17. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/MtlsAuthorizedClientRegistry_test.py +0 -0
  18. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/MtlsServerSslOptions.py +0 -0
  19. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/Payload.py +0 -0
  20. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/PayloadEndpoint.py +0 -0
  21. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/PayloadEndpointTest.py +0 -0
  22. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/PayloadEnvelope.py +0 -0
  23. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/PayloadFilterKeys.py +0 -0
  24. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/PayloadIO.py +0 -0
  25. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/PayloadPriority.py +0 -0
  26. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/PayloadResponse.py +0 -0
  27. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/PayloadResponseTest.py +0 -0
  28. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/PayloadResponse_test.py +0 -0
  29. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/PayloadTest.py +0 -0
  30. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/PerformTestActionTuple.py +0 -0
  31. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/SerialiseUtil.py +0 -0
  32. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/SerialiseUtil_test.py +0 -0
  33. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/TestTuple.py +0 -0
  34. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/Tuple.py +0 -0
  35. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/TupleAction.py +0 -0
  36. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/TupleBson.py +0 -0
  37. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/TupleBson_test.py +0 -0
  38. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/TupleDataForTest.py +0 -0
  39. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/TupleDocumentConverter.py +0 -0
  40. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/TuplePerfTest.py +0 -0
  41. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/TuplePolymorphicTupleTypeFieldArgTest.py +0 -0
  42. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/TupleRestful.py +0 -0
  43. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/TupleRestful_test.py +0 -0
  44. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/TupleSelector.py +0 -0
  45. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/TupleTest.py +0 -0
  46. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexABC.py +0 -0
  47. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexAccessControlContextABC.py +0 -0
  48. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexAuthenticatorABC.py +0 -0
  49. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexClientHttp.py +0 -0
  50. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexClientHttpTest.py +0 -0
  51. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexClientTcp.py +0 -0
  52. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexClientWebsocketFactory.py +0 -0
  53. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexConnectionABC.py +0 -0
  54. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexEndpointPermission.py +0 -0
  55. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexFactory.py +0 -0
  56. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexFactory_test.py +0 -0
  57. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexPayloadProtocol.py +0 -0
  58. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexPermission.py +0 -0
  59. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexPermissionError.py +0 -0
  60. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexServer.py +0 -0
  61. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexServerConnection.py +0 -0
  62. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexServerConnection_test.py +0 -0
  63. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexServerHttpResource.py +0 -0
  64. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexServerTcp.py +0 -0
  65. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexServerWebsocket.py +0 -0
  66. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexSession.py +0 -0
  67. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexSession_test.py +0 -0
  68. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexTcpMemoryLeakPayloadTest.py +0 -0
  69. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexTcpMemoryLeakRawTest.py +0 -0
  70. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexTest.py +0 -0
  71. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexUtil.py +0 -0
  72. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/VortexWritePushProducer.py +0 -0
  73. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/data_loader/TupleDataLiveLockManager.py +0 -0
  74. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/data_loader/TupleDataLoader.py +0 -0
  75. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/data_loader/TupleDataLoaderDelegate.py +0 -0
  76. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/data_loader/TupleDataLoaderTupleABC.py +0 -0
  77. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/data_loader/TupleDataLoaderTuples.py +0 -0
  78. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/data_loader/__init__.py +0 -0
  79. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/event_bus/TupleChangeEventABC.py +0 -0
  80. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/event_bus/TupleChangeEventBus.py +0 -0
  81. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/event_bus/TupleChangeEventBusObserverABC.py +0 -0
  82. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/event_bus/__init__.py +0 -0
  83. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/handler/TupleActionProcessorProxy.py +0 -0
  84. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/handler/TupleActionProcessorTestHandler.py +0 -0
  85. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/handler/TupleActionProcessor_shutdown_test.py +0 -0
  86. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/handler/TupleActionPushOffline.py +0 -0
  87. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/handler/TupleActionPushOffline_test.py +0 -0
  88. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/handler/TupleDataActionClient.py +0 -0
  89. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/handler/TupleDataObservableCache.py +0 -0
  90. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/handler/TupleDataObservableProxyHandler.py +0 -0
  91. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/handler/TupleDataObservableTestHandler.py +0 -0
  92. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/handler/TupleDataObserverClient.py +0 -0
  93. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/handler/TupleDataProviderABC.py +0 -0
  94. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/handler/VortexJSTupleLoaderTestHandler.py +0 -0
  95. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/handler/__init__.py +0 -0
  96. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/mypy_plugin.py +0 -0
  97. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/payload_many_tuples_benchmark_test.py +0 -0
  98. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/py.typed +0 -0
  99. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/restful/GzippedDataHttpClient.py +0 -0
  100. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/restful/GzippedDataHttpClientTest.py +0 -0
  101. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/restful/GzippedPayloadHttpClient.py +0 -0
  102. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/restful/RestfulHttpClient.py +0 -0
  103. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/restful/RestfulResource.py +0 -0
  104. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/restful/__init__.py +0 -0
  105. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/restful/restful_http_client_test.py +0 -0
  106. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/restful/restful_resource_test.py +0 -0
  107. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/rpc/RPC.py +0 -0
  108. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/rpc/RPCTest.py +0 -0
  109. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/rpc/RPC_test.py +0 -0
  110. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/rpc/__init__.py +0 -0
  111. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/sqla_orm/OrmCrudHandler.py +0 -0
  112. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/sqla_orm/TupleProviderForDb.py +0 -0
  113. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/sqla_orm/VortexTupleJson.py +0 -0
  114. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/sqla_orm/__init__.py +0 -0
  115. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/storage/TupleActionStorageSqlite.py +0 -0
  116. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/storage/TupleStorageSqlite.py +0 -0
  117. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/storage/__init__.py +0 -0
  118. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/tuple_json_schema/__init__.py +0 -0
  119. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/tuple_json_schema/constants.py +0 -0
  120. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/tuple_json_schema/json_schema_node_tuple.py +0 -0
  121. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/tuple_json_schema/tuple_json_schema_builder.py +0 -0
  122. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/tuple_json_schema/tuple_json_schema_test.py +0 -0
  123. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/tuple_json_schema/tuple_json_schema_test_tuples.py +0 -0
  124. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/tuple_test_fixtures.py +0 -0
  125. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/twisted_runtime_metrics.py +0 -0
  126. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortex/twisted_runtime_metrics_test.py +0 -0
  127. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortexpy.egg-info/dependency_links.txt +0 -0
  128. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortexpy.egg-info/entry_points.txt +0 -0
  129. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortexpy.egg-info/requires.txt +0 -0
  130. {vortexpy-4.3.3 → vortexpy-4.3.4}/vortexpy.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vortexpy
3
- Version: 4.3.3
3
+ Version: 4.3.4
4
4
  Summary: Flying Ion's observable, routable, data serialisation and transport code.
5
5
  Author-email: Flying Ion Pty Ltd <contact@flyingion.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vortexpy"
7
- version = "4.3.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."
@@ -6,4 +6,4 @@
6
6
  # Website : https://flyingion.com
7
7
  # Support : contact@flyingion.com
8
8
 
9
- __version__ = '4.3.3'
9
+ __version__ = '4.3.4'
@@ -188,7 +188,10 @@ class TupleActionProcessor:
188
188
  return tupleName in self._tupleProcessorsByTupleName
189
189
 
190
190
  def shutdown(self):
191
- self._endpoint.shutdown()
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 push access control.
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 for each subscribed observer before a server-push update is sent.
28
- Return ``True`` (or a Deferred that fires with ``True``) to allow delivery,
29
- ``False`` to silently skip that observer.
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 tupleName in self._tupleProvidersByTupleName, (
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
- auth.getEndpointPermissions, vs
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 _maybeDeferred(
513
- granter.isAccessGranted, filt, tupleSelector, ctx
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"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vortexpy
3
- Version: 4.3.3
3
+ Version: 4.3.4
4
4
  Summary: Flying Ion's observable, routable, data serialisation and transport code.
5
5
  Author-email: Flying Ion Pty Ltd <contact@flyingion.com>
6
6
  License: MIT
@@ -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