vortexpy 4.0.28__tar.gz → 4.1.1__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 (111) hide show
  1. {vortexpy-4.0.28/vortexpy.egg-info → vortexpy-4.1.1}/PKG-INFO +2 -24
  2. {vortexpy-4.0.28 → vortexpy-4.1.1}/pyproject.toml +2 -2
  3. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/PayloadEndpoint.py +1 -1
  4. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/PayloadEndpointTest.py +60 -49
  5. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/Tuple.py +17 -0
  6. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexFactory.py +8 -2
  7. vortexpy-4.1.1/vortex/__init__.py +9 -0
  8. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/event_bus/TupleChangeEventBus.py +23 -0
  9. vortexpy-4.1.1/vortex/tuple_json_schema/__init__.py +18 -0
  10. vortexpy-4.1.1/vortex/tuple_json_schema/constants.py +35 -0
  11. vortexpy-4.1.1/vortex/tuple_json_schema/json_schema_node_tuple.py +99 -0
  12. vortexpy-4.1.1/vortex/tuple_json_schema/tuple_json_schema_builder.py +225 -0
  13. vortexpy-4.1.1/vortex/tuple_json_schema/tuple_json_schema_test.py +257 -0
  14. vortexpy-4.1.1/vortex/tuple_json_schema/tuple_json_schema_test_tuples.py +69 -0
  15. {vortexpy-4.0.28 → vortexpy-4.1.1/vortexpy.egg-info}/PKG-INFO +2 -24
  16. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortexpy.egg-info/SOURCES.txt +6 -1
  17. vortexpy-4.1.1/vortexpy.egg-info/dependency_links.txt +1 -0
  18. vortexpy-4.0.28/LICENSE +0 -22
  19. vortexpy-4.0.28/vortex/__init__.py +0 -9
  20. vortexpy-4.0.28/vortex/py.typed +0 -0
  21. {vortexpy-4.0.28 → vortexpy-4.1.1}/README.md +0 -0
  22. {vortexpy-4.0.28 → vortexpy-4.1.1}/setup.cfg +0 -0
  23. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/DataWrapTuple.py +0 -0
  24. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/DeferUtil.py +0 -0
  25. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/Jsonable.py +0 -0
  26. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/Payload.py +0 -0
  27. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/PayloadEnvelope.py +0 -0
  28. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/PayloadFilterKeys.py +0 -0
  29. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/PayloadIO.py +0 -0
  30. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/PayloadPriority.py +0 -0
  31. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/PayloadResponse.py +0 -0
  32. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/PayloadResponseTest.py +0 -0
  33. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/PayloadTest.py +0 -0
  34. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/PerformTestActionTuple.py +0 -0
  35. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/SerialiseUtil.py +0 -0
  36. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/TestTuple.py +0 -0
  37. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/TupleAction.py +0 -0
  38. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/TupleDataForTest.py +0 -0
  39. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/TuplePerfTest.py +0 -0
  40. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/TuplePolymorphicTupleTypeFieldArgTest.py +0 -0
  41. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/TupleRestful.py +0 -0
  42. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/TupleRestfulTest.py +0 -0
  43. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/TupleSelector.py +0 -0
  44. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/TupleTest.py +0 -0
  45. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexABC.py +0 -0
  46. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexAccessControlContextABC.py +0 -0
  47. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexAuthenticatorABC.py +0 -0
  48. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexClientHttp.py +0 -0
  49. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexClientHttpTest.py +0 -0
  50. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexClientTcp.py +0 -0
  51. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexClientWebsocketFactory.py +0 -0
  52. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexConnectionABC.py +0 -0
  53. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexEndpointPermission.py +0 -0
  54. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexPayloadProtocol.py +0 -0
  55. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexPermission.py +0 -0
  56. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexPermissionError.py +0 -0
  57. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexServer.py +0 -0
  58. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexServerConnection.py +0 -0
  59. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexServerHttpResource.py +0 -0
  60. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexServerTcp.py +0 -0
  61. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexServerWebsocket.py +0 -0
  62. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexSession.py +0 -0
  63. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexTcpMemoryLeakPayloadTest.py +0 -0
  64. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexTcpMemoryLeakRawTest.py +0 -0
  65. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexTest.py +0 -0
  66. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexUtil.py +0 -0
  67. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/VortexWritePushProducer.py +0 -0
  68. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/data_loader/TupleDataLiveLockManager.py +0 -0
  69. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/data_loader/TupleDataLoader.py +0 -0
  70. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/data_loader/TupleDataLoaderDelegate.py +0 -0
  71. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/data_loader/TupleDataLoaderTupleABC.py +0 -0
  72. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/data_loader/TupleDataLoaderTuples.py +0 -0
  73. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/data_loader/__init__.py +0 -0
  74. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/event_bus/TupleChangeEventABC.py +0 -0
  75. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/event_bus/TupleChangeEventBusObserverABC.py +0 -0
  76. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/event_bus/__init__.py +0 -0
  77. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/handler/TupleActionProcessor.py +0 -0
  78. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/handler/TupleActionProcessorProxy.py +0 -0
  79. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/handler/TupleActionProcessorTestHandler.py +0 -0
  80. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/handler/TupleDataAccessGranterABC.py +0 -0
  81. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/handler/TupleDataActionClient.py +0 -0
  82. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/handler/TupleDataObservableCache.py +0 -0
  83. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/handler/TupleDataObservableHandler.py +0 -0
  84. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/handler/TupleDataObservableProxyHandler.py +0 -0
  85. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/handler/TupleDataObservableTestHandler.py +0 -0
  86. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/handler/TupleDataObserverClient.py +0 -0
  87. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/handler/TupleDataProviderABC.py +0 -0
  88. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/handler/VortexJSTupleLoaderTestHandler.py +0 -0
  89. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/handler/__init__.py +0 -0
  90. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/mypy_plugin.py +0 -0
  91. /vortexpy-4.0.28/vortexpy.egg-info/dependency_links.txt → /vortexpy-4.1.1/vortex/py.typed +0 -0
  92. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/restful/GzippedDataHttpClient.py +0 -0
  93. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/restful/GzippedDataHttpClientTest.py +0 -0
  94. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/restful/GzippedPayloadHttpClient.py +0 -0
  95. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/restful/RestfulHttpClient.py +0 -0
  96. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/restful/RestfulHttpClientTest.py +0 -0
  97. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/restful/RestfulResource.py +0 -0
  98. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/restful/RestfulResourceTest.py +0 -0
  99. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/restful/__init__.py +0 -0
  100. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/rpc/RPC.py +0 -0
  101. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/rpc/RPCTest.py +0 -0
  102. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/rpc/__init__.py +0 -0
  103. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/sqla_orm/OrmCrudHandler.py +0 -0
  104. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/sqla_orm/TupleProviderForDb.py +0 -0
  105. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/sqla_orm/VortexTupleJson.py +0 -0
  106. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/sqla_orm/__init__.py +0 -0
  107. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/storage/TupleStorageSqlite.py +0 -0
  108. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortex/storage/__init__.py +0 -0
  109. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortexpy.egg-info/entry_points.txt +0 -0
  110. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortexpy.egg-info/requires.txt +0 -0
  111. {vortexpy-4.0.28 → vortexpy-4.1.1}/vortexpy.egg-info/top_level.txt +0 -0
@@ -1,31 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vortexpy
3
- Version: 4.0.28
3
+ Version: 4.1.1
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
- License: MIT License
7
-
8
- Copyright (c) 2016-2026 Flying Ion Pty Ltd
9
-
10
- Permission is hereby granted, free of charge, to any person obtaining a copy
11
- of this software and associated documentation files (the "Software"), to deal
12
- in the Software without restriction, including without limitation the rights
13
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
- copies of the Software, and to permit persons to whom the Software is
15
- furnished to do so, subject to the following conditions:
16
-
17
- The above copyright notice and this permission notice shall be included in all
18
- copies or substantial portions of the Software.
19
-
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
- SOFTWARE.
27
-
28
-
6
+ License: MIT
29
7
  Project-URL: Homepage, https://flyingion.com
30
8
  Project-URL: Repository, https://gitlab.com/flyingion/vortexpy
31
9
  Project-URL: Issues, https://gitlab.com/flyingion/vortexpy/-/issues
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vortexpy"
7
- version = "4.0.28"
8
- license = {file = "LICENSE"}
7
+ version = "4.1.1"
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."
11
11
  readme = "README.md"
@@ -274,7 +274,7 @@ class PayloadEndpoint:
274
274
  )
275
275
  if self._callableAcceptsAccessControlContext:
276
276
  kwargs["accessControlContext"] = accessControlContext
277
- return callable_(**kwargs)
277
+ return (yield maybeDeferred(callable_, **kwargs))
278
278
  else:
279
279
  PayloadIO().remove(self)
280
280
 
@@ -6,6 +6,9 @@
6
6
  # Website : https://flyingion.com
7
7
  # Support : contact@flyingion.com
8
8
 
9
+ from twisted.internet import reactor
10
+ from twisted.internet.defer import Deferred
11
+ from twisted.internet.defer import inlineCallbacks
9
12
  from twisted.trial import unittest
10
13
 
11
14
  from vortex.Payload import Payload
@@ -19,7 +22,7 @@ class PayloadEndpointPyTestAssignPayload:
19
22
  self._ut = unitTest
20
23
 
21
24
  def process(self, payloadEnvelope: PayloadEnvelope, **kwargs):
22
- self._ut.deliveredPayload = payloadEnvelope
25
+ self._ut.deliveredPayloadEnvelope = payloadEnvelope
23
26
 
24
27
 
25
28
  class PayloadEndpointPyTest(unittest.TestCase):
@@ -28,46 +31,66 @@ class PayloadEndpointPyTest(unittest.TestCase):
28
31
 
29
32
  self.deliveredPayloadEnvelope = None
30
33
 
31
- def _testBuild(self, plFilt, epFilt):
34
+ @inlineCallbacks
35
+ def _flushReactor(self):
36
+ # PayloadIO schedules via callLater(0); PayloadEndpoint.process yields
37
+ # maybeDeferred — allow a few reactor turns to finish delivery.
38
+ for _ in range(3):
39
+ d = Deferred()
40
+ reactor.callLater(0, d.callback, None)
41
+ yield d
42
+
43
+ def _newPayload(self, plFilt: dict) -> Payload:
32
44
  payload = Payload()
33
45
  payload.filt.update(plFilt)
34
46
  for x in range(6):
35
47
  payload.filt["%s" % x] = x
48
+ return payload
36
49
 
37
- def processPayload(payloadEnvelope: PayloadEnvelope, **kwargs):
38
- self.deliveredPayloadEnvelope = payloadEnvelope
39
-
40
- self._keepFuncInScope = processPayload
41
-
42
- PayloadEndpoint(epFilt, processPayload, requiredPermissions=frozenset())
43
-
50
+ def _processEnvelope(self, payload: Payload) -> None:
51
+ # Build envelope without Payload.makePayloadEnvelope() — that path
52
+ # forbids the reactor thread (blocking encode).
44
53
  PayloadIO().process(
45
- payloadEnvelope=payload.makePayloadEnvelope(),
54
+ payloadEnvelope=PayloadEnvelope(filt=payload.filt),
46
55
  vortexUuid="test",
47
56
  vortexName="test",
48
57
  vortexSession=None,
49
58
  sendResponse=lambda x: x,
50
59
  )
51
60
 
61
+ def _registerAndProcess(self, plFilt, epFilt) -> Payload:
62
+ payload = self._newPayload(plFilt)
63
+
64
+ def processPayload(payloadEnvelope: PayloadEnvelope, **kwargs):
65
+ self.deliveredPayloadEnvelope = payloadEnvelope
66
+
67
+ self._keepFuncInScope = processPayload
68
+
69
+ PayloadEndpoint(epFilt, processPayload, requiredPermissions=frozenset())
70
+ self._processEnvelope(payload)
52
71
  return payload
53
72
 
73
+ @inlineCallbacks
54
74
  def testFiltMatches(self):
55
75
  plFilt = {"key": "unittest", "This matches": 555}
56
76
  epFilt = {"key": "unittest", "This matches": 555}
57
77
 
58
- payload = self._testBuild(plFilt, epFilt)
78
+ payload = self._registerAndProcess(plFilt, epFilt)
79
+ yield self._flushReactor()
59
80
 
60
81
  self.assertEqual(
61
- payload,
62
- self.deliveredPayloadEnvelope,
82
+ payload.filt,
83
+ self.deliveredPayloadEnvelope.filt,
63
84
  "PayloadIO/PayloadEndpoint delivery error",
64
85
  )
65
86
 
87
+ @inlineCallbacks
66
88
  def testFiltValueUnmatched(self):
67
89
  plFilt = {"key": "unittest", "This matches": 555}
68
90
  epFilt = {"key": "unittest", "This matches": 0}
69
91
 
70
- self._testBuild(plFilt, epFilt)
92
+ self._registerAndProcess(plFilt, epFilt)
93
+ yield self._flushReactor()
71
94
 
72
95
  self.assertEqual(
73
96
  self.deliveredPayloadEnvelope,
@@ -75,11 +98,13 @@ class PayloadEndpointPyTest(unittest.TestCase):
75
98
  "PayloadIO/PayloadEndpoint unmatched value test error",
76
99
  )
77
100
 
101
+ @inlineCallbacks
78
102
  def testFiltKeyUnmatched(self):
79
103
  plFilt = {"key": "unittest", "This matches": 555}
80
104
  epFilt = {"key": "unittest", "This doesnt matches": 555}
81
105
 
82
- self._testBuild(plFilt, epFilt)
106
+ self._registerAndProcess(plFilt, epFilt)
107
+ yield self._flushReactor()
83
108
 
84
109
  self.assertEqual(
85
110
  self.deliveredPayloadEnvelope,
@@ -87,11 +112,10 @@ class PayloadEndpointPyTest(unittest.TestCase):
87
112
  "PayloadIO/PayloadEndpoint unmatched value test error",
88
113
  )
89
114
 
115
+ @inlineCallbacks
90
116
  def testFunctionGoesOutOfScope(self):
91
117
  filt = {"key": "unittest", "This matches": 555}
92
-
93
- payload = Payload()
94
- payload.filt = filt
118
+ payload = self._newPayload(filt)
95
119
 
96
120
  def subScope():
97
121
  def outOfScopeFunc(
@@ -99,15 +123,12 @@ class PayloadEndpointPyTest(unittest.TestCase):
99
123
  ):
100
124
  self.deliveredPayloadEnvelope = payloadEnvelope
101
125
 
102
- PayloadEndpoint(filt, outOfScopeFunc, requiredPermissions=frozenset())
126
+ PayloadEndpoint(
127
+ filt, outOfScopeFunc, requiredPermissions=frozenset()
128
+ )
103
129
 
104
- PayloadIO().process(
105
- payloadEnvelope=payload.makePayloadEnvelope(),
106
- vortexUuid="test",
107
- vortexName="test",
108
- vortexSession=None,
109
- sendResponse=lambda x: x,
110
- )
130
+ self._processEnvelope(payload)
131
+ yield self._flushReactor()
111
132
 
112
133
  self.assertEqual(
113
134
  self.deliveredPayloadEnvelope,
@@ -115,23 +136,19 @@ class PayloadEndpointPyTest(unittest.TestCase):
115
136
  "PayloadIO/PayloadEndpoint unmatched value test error",
116
137
  )
117
138
 
139
+ @inlineCallbacks
118
140
  def testClassGoesOutOdScope(self):
119
141
  filt = {"key": "unittest", "This matches": 555}
120
-
121
- payload = Payload()
122
- payload.filt = filt
142
+ payload = self._newPayload(filt)
123
143
 
124
144
  def subScope():
125
145
  inst = PayloadEndpointPyTestAssignPayload(self)
126
- PayloadEndpoint(filt, inst.process, requiredPermissions=frozenset())
146
+ PayloadEndpoint(
147
+ filt, inst.process, requiredPermissions=frozenset()
148
+ )
127
149
 
128
- PayloadIO().process(
129
- payloadEnvelope=payload.makePayloadEnvelope(),
130
- vortexUuid="test",
131
- vortexName="test",
132
- vortexSession=None,
133
- sendResponse=lambda x: x,
134
- )
150
+ self._processEnvelope(payload)
151
+ yield self._flushReactor()
135
152
 
136
153
  self.assertEqual(
137
154
  self.deliveredPayloadEnvelope,
@@ -139,25 +156,19 @@ class PayloadEndpointPyTest(unittest.TestCase):
139
156
  "PayloadIO/PayloadEndpoint unmatched value test error",
140
157
  )
141
158
 
159
+ @inlineCallbacks
142
160
  def testClassStaysInScope(self):
143
161
  filt = {"key": "unittest", "This matches": 555}
144
-
145
- payload = Payload()
146
- payload.filt = filt
162
+ payload = self._newPayload(filt)
147
163
 
148
164
  inst = PayloadEndpointPyTestAssignPayload(self)
149
165
  PayloadEndpoint(filt, inst.process, requiredPermissions=frozenset())
150
166
 
151
- PayloadIO().process(
152
- payloadEnvelope=payload.makePayloadEnvelope(),
153
- vortexUuid="test",
154
- vortexName="test",
155
- vortexSession=None,
156
- sendResponse=lambda x: x,
157
- )
167
+ self._processEnvelope(payload)
168
+ yield self._flushReactor()
158
169
 
159
170
  self.assertEqual(
160
- self.deliveredPayloadEnvelope,
161
- payload,
171
+ payload.filt,
172
+ self.deliveredPayloadEnvelope.filt,
162
173
  "PayloadIO/PayloadEndpoint unmatched value test error",
163
174
  )
@@ -595,6 +595,23 @@ class Tuple(Jsonable, _TupleToPlainJsonMixin, _TupleToSqlaJsonMixin):
595
595
  def tupleFieldNames(cls) -> List[str]:
596
596
  return cls.__fieldNames__
597
597
 
598
+ @classmethod
599
+ def tupleJsonSchema(cls) -> "JsonSchemaNodeTuple":
600
+ """Tuple JSON Schema
601
+
602
+ Build a typed Draft 2020-12 subset schema tree from this tuple's
603
+ ``TupleField`` metadata.
604
+
605
+ :return: Root ``JsonSchemaNodeTuple``. Call ``buildJsonSchemaDict()``
606
+ for Draft 2020-12 JSON Schema keys, or ``tupleToRestfulJsonDict()``
607
+ for Vortex restful JSON.
608
+ """
609
+ from vortex.tuple_json_schema.tuple_json_schema_builder import (
610
+ TupleJsonSchemaBuilder,
611
+ )
612
+
613
+ return TupleJsonSchemaBuilder.buildJsonSchema(cls)
614
+
598
615
  @classmethod
599
616
  def tupleName(cls): # DEPRECIATED
600
617
  return cls.__tupleType__
@@ -446,8 +446,14 @@ class VortexFactory:
446
446
  yield deferLater(reactor, 0.05, lambda: None)
447
447
 
448
448
  while cls.__listeningPorts:
449
- cls.__listeningPorts.pop().stopListening()
450
- yield deferLater(reactor, 0.05, lambda: None)
449
+ stopDeferred = cls.__listeningPorts.pop().stopListening()
450
+ if stopDeferred is not None:
451
+ yield stopDeferred
452
+
453
+ # Let Twisted finish unregistering closed ports from the reactor
454
+ # before before-shutdown returns — avoids epoll Bad file descriptor
455
+ # spam when other before-shutdown handlers close sockets next.
456
+ yield deferLater(reactor, 0.05, lambda: None)
451
457
 
452
458
  cls.__vortexInfoState = _VortexFactoryVortexInfo()
453
459
 
@@ -0,0 +1,9 @@
1
+ # Copyright (c) 2016-2026 Flying Ion Pty Ltd
2
+ #
3
+ # This software is open source; the MIT license applies.
4
+ # See LICENSE for the full license text.
5
+ #
6
+ # Website : https://flyingion.com
7
+ # Support : contact@flyingion.com
8
+
9
+ __version__ = '4.1.1'
@@ -163,6 +163,29 @@ class TupleChangeEventBus:
163
163
 
164
164
  self._weakObserversBySelectorName[eventName].add(observer)
165
165
 
166
+ def removeObserver(
167
+ self,
168
+ observer: TupleChangeEventBusObserverABC,
169
+ eventNameOrClass: Optional[
170
+ Union[
171
+ str,
172
+ Type[TupleChangeEventABC],
173
+ TupleChangeEventABC,
174
+ ]
175
+ ] = None,
176
+ ) -> None:
177
+ assert isinstance(
178
+ observer, TupleChangeEventBusObserverABC
179
+ ), "observer must be a TupleChangeEventBusObserverABC"
180
+
181
+ if eventNameOrClass is None:
182
+ self._weakObserversForAllTupleChangeEventABCNames.discard(observer)
183
+ return
184
+
185
+ eventName = self._nameFromEvent(eventNameOrClass)
186
+
187
+ self._weakObserversBySelectorName[eventName].discard(observer)
188
+
166
189
  # noinspection PyMethodMayBeStatic
167
190
  def _nameFromEvent(
168
191
  self,
@@ -0,0 +1,18 @@
1
+ # Copyright (c) 2016-2026 Flying Ion Pty Ltd
2
+ #
3
+ # This software is open source; the MIT license applies.
4
+ # See LICENSE for the full license text.
5
+ #
6
+ # Website : https://flyingion.com
7
+ # Support : contact@flyingion.com
8
+
9
+ """Tuple JSON Schema — typed Vortex tuples for Draft 2020-12 subset schemas.
10
+
11
+ Import from submodules::
12
+
13
+ from vortex.tuple_json_schema.json_schema_node_tuple import JsonSchemaNodeTuple
14
+ from vortex.tuple_json_schema.tuple_json_schema_builder import (
15
+ TupleJsonSchemaBuilder,
16
+ )
17
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_TYPE_STRING
18
+ """
@@ -0,0 +1,35 @@
1
+ # Copyright (c) 2016-2026 Flying Ion Pty Ltd
2
+ #
3
+ # This software is open source; the MIT license applies.
4
+ # See LICENSE for the full license text.
5
+ #
6
+ # Website : https://flyingion.com
7
+ # Support : contact@flyingion.com
8
+
9
+ """JSON Schema string constants for Draft 2020-12 subset."""
10
+
11
+ JSON_SCHEMA_DRAFT_2020_12 = "https://json-schema.org/draft/2020-12/schema"
12
+
13
+ JSON_SCHEMA_KEY_SCHEMA = "$schema"
14
+ JSON_SCHEMA_KEY_DEFS = "$defs"
15
+ JSON_SCHEMA_KEY_REF = "$ref"
16
+ JSON_SCHEMA_KEY_TYPE = "type"
17
+ JSON_SCHEMA_KEY_TITLE = "title"
18
+ JSON_SCHEMA_KEY_PROPERTIES = "properties"
19
+ JSON_SCHEMA_KEY_REQUIRED = "required"
20
+ JSON_SCHEMA_KEY_ITEMS = "items"
21
+ JSON_SCHEMA_KEY_FORMAT = "format"
22
+ JSON_SCHEMA_KEY_CONTENT_ENCODING = "contentEncoding"
23
+ JSON_SCHEMA_KEY_DESCRIPTION = "description"
24
+
25
+ JSON_SCHEMA_TYPE_OBJECT = "object"
26
+ JSON_SCHEMA_TYPE_STRING = "string"
27
+ JSON_SCHEMA_TYPE_INTEGER = "integer"
28
+ JSON_SCHEMA_TYPE_NUMBER = "number"
29
+ JSON_SCHEMA_TYPE_BOOLEAN = "boolean"
30
+ JSON_SCHEMA_TYPE_ARRAY = "array"
31
+
32
+ JSON_SCHEMA_FORMAT_DATE_TIME = "date-time"
33
+ JSON_SCHEMA_CONTENT_ENCODING_BINARY = "binary"
34
+
35
+ JSON_SCHEMA_DEFS_REF_PREFIX = f"#/{JSON_SCHEMA_KEY_DEFS}/"
@@ -0,0 +1,99 @@
1
+ # Copyright (c) 2016-2026 Flying Ion Pty Ltd
2
+ #
3
+ # This software is open source; the MIT license applies.
4
+ # See LICENSE for the full license text.
5
+ #
6
+ # Website : https://flyingion.com
7
+ # Support : contact@flyingion.com
8
+
9
+ """JSON Schema node as a Vortex tuple (Draft 2020-12 subset)."""
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any
14
+
15
+ from vortex.Tuple import Tuple
16
+ from vortex.Tuple import TupleField
17
+ from vortex.Tuple import addTupleType
18
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_CONTENT_ENCODING
19
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_DEFS
20
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_DESCRIPTION
21
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_FORMAT
22
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_ITEMS
23
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_PROPERTIES
24
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_REF
25
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_REQUIRED
26
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_SCHEMA
27
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_TITLE
28
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_TYPE
29
+
30
+
31
+ @addTupleType
32
+ class JsonSchemaNodeTuple(Tuple):
33
+ """JSON Schema Node Tuple
34
+
35
+ Typed Draft 2020-12 subset node. Application code uses this tuple;
36
+ call ``buildJsonSchemaDict()`` only at a boundary that needs raw JSON Schema
37
+ (``$schema`` / ``$ref`` / ``$defs`` keys). Prefer
38
+ ``tupleToRestfulJsonDict()`` when serialising as a normal Vortex tuple.
39
+ """
40
+
41
+ __tupleType__ = "flyingion.vortex.JsonSchemaNodeTuple"
42
+
43
+ schemaUri: str | None = TupleField(None, typingType=str | None)
44
+ schemaType: str | None = TupleField(None, typingType=str | None)
45
+ title: str | None = TupleField(None, typingType=str | None)
46
+ description: str | None = TupleField(None, typingType=str | None)
47
+ format: str | None = TupleField(None, typingType=str | None)
48
+ contentEncoding: str | None = TupleField(None, typingType=str | None)
49
+ ref: str | None = TupleField(None, typingType=str | None)
50
+ items: JsonSchemaNodeTuple | None = TupleField(None)
51
+ properties: dict[str, JsonSchemaNodeTuple] | None = TupleField(None)
52
+ required: list[str] | None = TupleField(None, typingType=list[str] | None)
53
+ defs: dict[str, JsonSchemaNodeTuple] | None = TupleField(None)
54
+
55
+ def buildJsonSchemaDict(self) -> dict[str, Any]:
56
+ """Build JSON Schema Dict
57
+
58
+ Emit a Draft 2020-12 subset dict with JSON Schema keys
59
+ (``$schema``, ``$ref``, ``$defs``, ``type``, …).
60
+
61
+ :return: JSON-serialisable schema dict.
62
+ """
63
+ out: dict[str, Any] = {}
64
+ if self.schemaUri is not None:
65
+ out[JSON_SCHEMA_KEY_SCHEMA] = self.schemaUri
66
+ if self.schemaType is not None:
67
+ out[JSON_SCHEMA_KEY_TYPE] = self.schemaType
68
+ if self.title is not None:
69
+ out[JSON_SCHEMA_KEY_TITLE] = self.title
70
+ if self.description is not None:
71
+ out[JSON_SCHEMA_KEY_DESCRIPTION] = self.description
72
+ if self.format is not None:
73
+ out[JSON_SCHEMA_KEY_FORMAT] = self.format
74
+ if self.contentEncoding is not None:
75
+ out[JSON_SCHEMA_KEY_CONTENT_ENCODING] = self.contentEncoding
76
+ if self.ref is not None:
77
+ out[JSON_SCHEMA_KEY_REF] = self.ref
78
+ if self.items is not None:
79
+ out[JSON_SCHEMA_KEY_ITEMS] = self.items.buildJsonSchemaDict()
80
+ if self.properties is not None:
81
+ out[JSON_SCHEMA_KEY_PROPERTIES] = {
82
+ name: node.buildJsonSchemaDict()
83
+ for name, node in self.properties.items()
84
+ }
85
+ if self.required is not None:
86
+ out[JSON_SCHEMA_KEY_REQUIRED] = list(self.required)
87
+ if self.defs is not None:
88
+ out[JSON_SCHEMA_KEY_DEFS] = {
89
+ name: node.buildJsonSchemaDict()
90
+ for name, node in self.defs.items()
91
+ }
92
+ return out
93
+
94
+
95
+ JsonSchemaNodeTuple.items.typingType = JsonSchemaNodeTuple | None
96
+ JsonSchemaNodeTuple.properties.typingType = (
97
+ dict[str, JsonSchemaNodeTuple] | None
98
+ )
99
+ JsonSchemaNodeTuple.defs.typingType = dict[str, JsonSchemaNodeTuple] | None