vortexpy 4.3.5__tar.gz → 6.0.0__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 (139) hide show
  1. vortexpy-6.0.0/LICENSE +22 -0
  2. {vortexpy-4.3.5 → vortexpy-6.0.0}/PKG-INFO +10 -7
  3. {vortexpy-4.3.5 → vortexpy-6.0.0}/README.md +9 -6
  4. {vortexpy-4.3.5 → vortexpy-6.0.0}/pyproject.toml +1 -11
  5. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/Jsonable.py +2 -4
  6. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadEndpoint.py +31 -39
  7. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadEndpointTest.py +3 -2
  8. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadEnvelope.py +9 -9
  9. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadIO.py +11 -13
  10. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadPriority.py +1 -0
  11. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadResponse.py +3 -8
  12. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/SerialiseUtil.py +6 -42
  13. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/Tuple.py +9 -34
  14. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/TuplePolymorphicTupleTypeFieldArgTest.py +1 -4
  15. vortexpy-4.3.5/vortex/TupleDocumentConverter.py → vortexpy-6.0.0/vortex/TupleRestful.py +89 -273
  16. vortexpy-6.0.0/vortex/TupleRestfulTest.py +267 -0
  17. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexAccessControlContextABC.py +47 -13
  18. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexAuthenticatorABC.py +6 -1
  19. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexFactory.py +1 -9
  20. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexPayloadProtocol.py +4 -9
  21. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexPermission.py +0 -1
  22. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexPermissionError.py +0 -1
  23. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexServer.py +18 -1
  24. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexServerConnection.py +2 -2
  25. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexServerWebsocket.py +4 -2
  26. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexSession.py +65 -14
  27. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexTcpMemoryLeakPayloadTest.py +24 -24
  28. vortexpy-6.0.0/vortex/VortexUserDetailsABC.py +40 -0
  29. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexWritePushProducer.py +53 -26
  30. vortexpy-6.0.0/vortex/VortexWritePushProducer_test.py +75 -0
  31. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/__init__.py +1 -1
  32. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/data_loader/TupleDataLiveLockManager.py +147 -65
  33. vortexpy-6.0.0/vortex/data_loader/TupleDataLiveLockManager_test.py +336 -0
  34. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/data_loader/TupleDataLoader.py +16 -28
  35. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/data_loader/TupleDataLoaderDelegate.py +4 -14
  36. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/data_loader/TupleDataLoaderTuples.py +13 -9
  37. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/event_bus/TupleChangeEventBus.py +8 -7
  38. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleActionProcessor.py +87 -103
  39. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleActionProcessorProxy.py +13 -13
  40. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleActionProcessorTestHandler.py +3 -3
  41. vortexpy-6.0.0/vortex/handler/TupleActionProcessor_test.py +147 -0
  42. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleDataAccessGranterABC.py +4 -4
  43. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleDataObservableHandler.py +99 -101
  44. vortexpy-6.0.0/vortex/handler/TupleDataObservableHandler_test.py +280 -0
  45. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleDataObservableProxyHandler.py +37 -39
  46. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleDataObserverClient.py +2 -3
  47. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleDataProviderABC.py +1 -3
  48. vortexpy-6.0.0/vortex/restful/RestfulHttpClientTest.py +68 -0
  49. vortexpy-6.0.0/vortex/restful/RestfulResourceTest.py +108 -0
  50. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/rpc/RPC.py +44 -73
  51. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/sqla_orm/OrmCrudHandler.py +34 -8
  52. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/storage/TupleStorageSqlite.py +3 -3
  53. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/tuple_json_schema/tuple_json_schema_builder.py +1 -3
  54. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/tuple_json_schema/tuple_json_schema_test.py +2 -4
  55. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/twisted_runtime_metrics.py +48 -91
  56. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/twisted_runtime_metrics_test.py +7 -7
  57. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortexpy.egg-info/PKG-INFO +10 -7
  58. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortexpy.egg-info/SOURCES.txt +8 -11
  59. vortexpy-4.3.5/vortex/PayloadResponse_test.py +0 -42
  60. vortexpy-4.3.5/vortex/SerialiseUtil_test.py +0 -123
  61. vortexpy-4.3.5/vortex/TupleBson.py +0 -144
  62. vortexpy-4.3.5/vortex/TupleBson_test.py +0 -221
  63. vortexpy-4.3.5/vortex/TupleRestful.py +0 -134
  64. vortexpy-4.3.5/vortex/TupleRestful_test.py +0 -209
  65. vortexpy-4.3.5/vortex/handler/TupleDataObservableHandler_test.py +0 -264
  66. vortexpy-4.3.5/vortex/payload_many_tuples_benchmark_test.py +0 -71
  67. vortexpy-4.3.5/vortex/restful/restful_http_client_test.py +0 -73
  68. vortexpy-4.3.5/vortex/restful/restful_resource_test.py +0 -76
  69. vortexpy-4.3.5/vortex/rpc/RPC_test.py +0 -114
  70. vortexpy-4.3.5/vortex/tuple_test_fixtures.py +0 -184
  71. {vortexpy-4.3.5 → vortexpy-6.0.0}/setup.cfg +0 -0
  72. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/DataWrapTuple.py +0 -0
  73. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/DeferUtil.py +0 -0
  74. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/DeferUtil_test.py +0 -0
  75. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/MtlsAuthorizedClientRegistry.py +0 -0
  76. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/MtlsAuthorizedClientRegistry_test.py +0 -0
  77. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/MtlsServerSslOptions.py +0 -0
  78. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/Payload.py +0 -0
  79. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadFilterKeys.py +0 -0
  80. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadResponseTest.py +0 -0
  81. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadTest.py +0 -0
  82. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PerformTestActionTuple.py +0 -0
  83. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/TestTuple.py +0 -0
  84. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/TupleAction.py +0 -0
  85. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/TupleDataForTest.py +0 -0
  86. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/TuplePerfTest.py +0 -0
  87. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/TupleSelector.py +0 -0
  88. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/TupleTest.py +0 -0
  89. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexABC.py +0 -0
  90. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexClientHttp.py +0 -0
  91. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexClientHttpTest.py +0 -0
  92. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexClientTcp.py +0 -0
  93. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexClientWebsocketFactory.py +0 -0
  94. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexConnectionABC.py +0 -0
  95. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexEndpointPermission.py +0 -0
  96. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexFactory_test.py +0 -0
  97. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexServerConnection_test.py +0 -0
  98. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexServerHttpResource.py +0 -0
  99. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexServerTcp.py +0 -0
  100. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexSession_test.py +0 -0
  101. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexTcpMemoryLeakRawTest.py +0 -0
  102. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexTest.py +0 -0
  103. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexUtil.py +0 -0
  104. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/data_loader/TupleDataLoaderTupleABC.py +0 -0
  105. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/data_loader/__init__.py +0 -0
  106. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/event_bus/TupleChangeEventABC.py +0 -0
  107. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/event_bus/TupleChangeEventBusObserverABC.py +0 -0
  108. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/event_bus/__init__.py +0 -0
  109. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleActionProcessor_shutdown_test.py +0 -0
  110. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleActionPushOffline.py +0 -0
  111. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleActionPushOffline_test.py +0 -0
  112. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleDataActionClient.py +0 -0
  113. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleDataObservableCache.py +0 -0
  114. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleDataObservableTestHandler.py +0 -0
  115. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/VortexJSTupleLoaderTestHandler.py +0 -0
  116. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/__init__.py +0 -0
  117. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/mypy_plugin.py +0 -0
  118. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/py.typed +0 -0
  119. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/restful/GzippedDataHttpClient.py +0 -0
  120. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/restful/GzippedDataHttpClientTest.py +0 -0
  121. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/restful/GzippedPayloadHttpClient.py +0 -0
  122. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/restful/RestfulHttpClient.py +0 -0
  123. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/restful/RestfulResource.py +0 -0
  124. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/restful/__init__.py +0 -0
  125. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/rpc/RPCTest.py +0 -0
  126. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/rpc/__init__.py +0 -0
  127. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/sqla_orm/TupleProviderForDb.py +0 -0
  128. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/sqla_orm/VortexTupleJson.py +0 -0
  129. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/sqla_orm/__init__.py +0 -0
  130. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/storage/TupleActionStorageSqlite.py +0 -0
  131. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/storage/__init__.py +0 -0
  132. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/tuple_json_schema/__init__.py +0 -0
  133. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/tuple_json_schema/constants.py +0 -0
  134. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/tuple_json_schema/json_schema_node_tuple.py +0 -0
  135. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/tuple_json_schema/tuple_json_schema_test_tuples.py +0 -0
  136. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortexpy.egg-info/dependency_links.txt +0 -0
  137. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortexpy.egg-info/entry_points.txt +0 -0
  138. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortexpy.egg-info/requires.txt +0 -0
  139. {vortexpy-4.3.5 → vortexpy-6.0.0}/vortexpy.egg-info/top_level.txt +0 -0
vortexpy-6.0.0/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016-2026 Flying Ion Pty Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vortexpy
3
- Version: 4.3.5
3
+ Version: 6.0.0
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
@@ -32,17 +32,20 @@ Requires-Dist: txhttputil>=1.2.0
32
32
  Flying Ion's observable, routable, data serialisation and transport code.
33
33
 
34
34
  See [README_PERMISSIONS.md](README_PERMISSIONS.md) for the two-level permission
35
- system (endpoint guards + access control context) introduced in v5.0.
36
-
37
- **Tuple and Payload tutorials:** [docs/README.md](docs/README.md) (simple → full features, encode/decode, threaded decode).
35
+ system (endpoint guards + access control context) introduced in v6.0.
38
36
 
39
37
  Why PY at the end? To be consistent with VortexJS, which is the Browser side package.
40
38
  See https://gitlab.com/flyingion/vortexjs
41
39
 
42
40
  Requirements:
43
- * Python 3.5
44
- * Twisted 16+
45
- * SQLAlchemy 1, 1.1
41
+ * Python 3.12 (development target)
42
+ * Twisted (autobahn / txwebsocket)
43
+ * SQLAlchemy >= 2.0
44
+
45
+ See `pyproject.toml` for the authoritative dependency list.
46
+
47
+ See [README_MIGRATION.md](README_MIGRATION.md) for breaking changes and
48
+ migration guides (4.4.0, 6.0.0).
46
49
 
47
50
  The "vortex" is designed to transport "Payloads" from a web browser (VortexJS) to a
48
51
  twisted web server (VortexPY). There is also a python client for python client to python server
@@ -3,17 +3,20 @@
3
3
  Flying Ion's observable, routable, data serialisation and transport code.
4
4
 
5
5
  See [README_PERMISSIONS.md](README_PERMISSIONS.md) for the two-level permission
6
- system (endpoint guards + access control context) introduced in v5.0.
7
-
8
- **Tuple and Payload tutorials:** [docs/README.md](docs/README.md) (simple → full features, encode/decode, threaded decode).
6
+ system (endpoint guards + access control context) introduced in v6.0.
9
7
 
10
8
  Why PY at the end? To be consistent with VortexJS, which is the Browser side package.
11
9
  See https://gitlab.com/flyingion/vortexjs
12
10
 
13
11
  Requirements:
14
- * Python 3.5
15
- * Twisted 16+
16
- * SQLAlchemy 1, 1.1
12
+ * Python 3.12 (development target)
13
+ * Twisted (autobahn / txwebsocket)
14
+ * SQLAlchemy >= 2.0
15
+
16
+ See `pyproject.toml` for the authoritative dependency list.
17
+
18
+ See [README_MIGRATION.md](README_MIGRATION.md) for breaking changes and
19
+ migration guides (4.4.0, 6.0.0).
17
20
 
18
21
  The "vortex" is designed to transport "Payloads" from a web browser (VortexJS) to a
19
22
  twisted web server (VortexPY). There is also a python client for python client to python server
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vortexpy"
7
- version = "4.3.5"
7
+ version = "6.0.0"
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."
@@ -53,13 +53,3 @@ namespaces = false
53
53
 
54
54
  [project.entry-points."mypy.plugins"]
55
55
  vortex_defer = "vortex.mypy_plugin:plugin"
56
-
57
- [tool.pytest.ini_options]
58
- testpaths = ["vortex"]
59
- python_files = ["*_test.py"]
60
- python_functions = ["test*"]
61
- norecursedirs = ["_trial_temp", "example", "old_fork"]
62
- markers = [
63
- "trial: legacy Twisted Trial *Test.py modules (not collected; run with trial CLI)",
64
- "slow: large payload or timing benchmarks",
65
- ]
@@ -107,11 +107,9 @@ class Jsonable(object):
107
107
  try:
108
108
  valueType = V_NULL if value is None else toRapuiType(value)
109
109
  except KeyError as e:
110
- unsupportedType = e.args[0] if e.args else value.__class__
111
110
  raise KeyError(
112
- "%r field name is %r, type is %r"
113
- % (unsupportedType, name, value.__class__)
114
- ) from e
111
+ "%s field name is %s, type is %s", e, name, value.__class__
112
+ )
115
113
 
116
114
  # Payloads and Tuples
117
115
  if self.__isRawJsonableField(name):
@@ -22,32 +22,18 @@ from vortex.VortexAccessControlContextABC import (
22
22
  NO_AUTH_ACCESS_CONTROL_CONTEXT,
23
23
  VortexAccessControlContextABC,
24
24
  )
25
- from vortex.VortexEndpointPermission import (
26
- EndpointPermissionsType as PermissionsType,
27
- )
25
+ from vortex.VortexEndpointPermission import EndpointPermissionsType as PermissionsType
28
26
  from vortex.VortexSession import (
29
27
  _callableLabel,
30
28
  checkAccessControlContextSignature,
29
+ checkEndpointPermissionsSignature,
31
30
  checkNoHttpSessionInSignature,
31
+ checkNoVortexSessionInSignature,
32
32
  )
33
33
 
34
34
  logger = logging.getLogger(__name__)
35
35
 
36
36
 
37
- # TODO: typings needs to support keyword args before tbis will work.
38
- # PayloadEndpointProcessCallable = Callable[
39
- # [
40
- # ...
41
- # # PayloadEnvelope, # payloadEnvelope
42
- # # str, # vortexUuid
43
- # # str, # vortexName
44
- # # Any, # vortexSession
45
- # # SendVortexMsgResponseCallable # sendResponse
46
- # ],
47
- # Optional[Deferred] # Return
48
- # ]
49
-
50
-
51
37
  class PayloadEndpoint:
52
38
  """
53
39
  The payload endpoint is responsible for matching payloads filters against
@@ -71,7 +57,6 @@ class PayloadEndpoint:
71
57
  acceptOnlyFromVortex: Optional[Union[str, tuple]] = None,
72
58
  ignoreFromVortex: Optional[Union[str, tuple]] = None,
73
59
  requiredPermissions: Optional[PermissionsType] = None,
74
- logProcessException: bool = True,
75
60
  ) -> None:
76
61
  """
77
62
  :param filt: The filter to match against payloads
@@ -83,13 +68,8 @@ class PayloadEndpoint:
83
68
  the caller's session must hold. Pass an empty frozenset to
84
69
  explicitly declare "no permissions required". Omitting the
85
70
  parameter (``None``) emits a deprecation warning.
86
- :param logProcessException: When True, handler exceptions log at ERROR.
87
- When False, the same events log at DEBUG with neutral wording.
88
71
  """
89
- self._requiredPermissions: Optional[PermissionsType] = (
90
- requiredPermissions
91
- )
92
- self._logProcessException = logProcessException
72
+ self._requiredPermissions: Optional[PermissionsType] = requiredPermissions
93
73
 
94
74
  if requiredPermissions is None:
95
75
  logger.warning(self._NO_PERMISSIONS_WARNING, filt)
@@ -97,13 +77,18 @@ class PayloadEndpoint:
97
77
  callableDesc = (
98
78
  f"PayloadEndpoint filt={filt} callback={_callableLabel(callable_)}"
99
79
  )
100
- self._callableAcceptsAccessControlContext = (
101
- checkAccessControlContextSignature(
80
+ self._callableAcceptsAccessControlContext = checkAccessControlContextSignature(
81
+ callable_,
82
+ callableDescription=callableDesc,
83
+ )
84
+ self._callableAcceptsEndpointPermissions = (
85
+ checkEndpointPermissionsSignature(
102
86
  callable_,
103
87
  callableDescription=callableDesc,
104
88
  )
105
89
  )
106
90
  checkNoHttpSessionInSignature(callable_, callableDesc)
91
+ checkNoVortexSessionInSignature(callable_, callableDesc)
107
92
 
108
93
  self._acceptOnlyFromVortex = acceptOnlyFromVortex
109
94
  if isinstance(self._acceptOnlyFromVortex, str):
@@ -178,10 +163,7 @@ class PayloadEndpoint:
178
163
  return False
179
164
 
180
165
  # Filter for the backend vortexes.
181
- if (
182
- self._acceptOnlyFromVortex
183
- and vortexName not in self._acceptOnlyFromVortex
184
- ):
166
+ if self._acceptOnlyFromVortex and vortexName not in self._acceptOnlyFromVortex:
185
167
  return False
186
168
 
187
169
  def removeUnhashable(filt):
@@ -225,14 +207,13 @@ class PayloadEndpoint:
225
207
  if not self.check(payloadEnvelope, vortexName):
226
208
  return
227
209
 
228
- if self.PERMITTER and not self.PERMITTER.check(
229
- payloadEnvelope, vortexSession
230
- ):
210
+ if self.PERMITTER and not self.PERMITTER.check(payloadEnvelope, vortexSession):
231
211
  logger.debug("Permission failed for %s", payloadEnvelope.filt)
232
212
  return
233
213
 
234
214
  # Level 1: Endpoint permission check
235
215
  auth = VortexFactory.getAuthenticator()
216
+ endpointPermissions = None
236
217
  if self._requiredPermissions:
237
218
  if not auth:
238
219
  logger.error(
@@ -259,16 +240,26 @@ class PayloadEndpoint:
259
240
  )
260
241
  return
261
242
 
243
+ # Level 1 injection: when the callable declares endpointPermissions,
244
+ # compute the caller's permissions once here at the boundary so
245
+ # handlers never see the session or the authenticator.
246
+ if (
247
+ self._callableAcceptsEndpointPermissions
248
+ and endpointPermissions is None
249
+ and auth
250
+ and vortexSession
251
+ ):
252
+ endpointPermissions = yield maybeDeferred(
253
+ auth.getEndpointPermissions, vortexSession
254
+ )
255
+
262
256
  # Level 2: Get access control context, but only if the callback uses it.
263
257
  # Default to the sentinel so handlers always receive a non-None value
264
258
  # with a clear error on attribute access when auth is unavailable.
265
259
  accessControlContext = NO_AUTH_ACCESS_CONTROL_CONTEXT
266
260
  if self._callableAcceptsAccessControlContext and auth and vortexSession:
267
261
  raw_ctx = yield maybeDeferred(
268
- auth.getAccessControlContext,
269
- vortexSession,
270
- vortexName,
271
- vortexUuid,
262
+ auth.getAccessControlContext, vortexSession, vortexName, vortexUuid
272
263
  )
273
264
  if not isinstance(raw_ctx, VortexAccessControlContextABC):
274
265
  logger.error(
@@ -283,15 +274,16 @@ class PayloadEndpoint:
283
274
 
284
275
  callable_ = self._wref()
285
276
  if callable_:
286
- kwargs = dict(
277
+ kwargs: Dict[str, object] = dict(
287
278
  payloadEnvelope=payloadEnvelope,
288
279
  vortexUuid=vortexUuid,
289
280
  vortexName=vortexName,
290
281
  sendResponse=sendResponse,
291
- vortexSession=vortexSession,
292
282
  )
293
283
  if self._callableAcceptsAccessControlContext:
294
284
  kwargs["accessControlContext"] = accessControlContext
285
+ if self._callableAcceptsEndpointPermissions:
286
+ kwargs["endpointPermissions"] = endpointPermissions
295
287
  return (yield maybeDeferred(callable_, **kwargs))
296
288
  else:
297
289
  PayloadIO().remove(self)
@@ -54,7 +54,6 @@ class PayloadEndpointPyTest(unittest.TestCase):
54
54
  payloadEnvelope=PayloadEnvelope(filt=payload.filt),
55
55
  vortexUuid="test",
56
56
  vortexName="test",
57
- vortexSession=None,
58
57
  sendResponse=lambda x: x,
59
58
  )
60
59
 
@@ -143,7 +142,9 @@ class PayloadEndpointPyTest(unittest.TestCase):
143
142
 
144
143
  def subScope():
145
144
  inst = PayloadEndpointPyTestAssignPayload(self)
146
- PayloadEndpoint(filt, inst.process, requiredPermissions=frozenset())
145
+ PayloadEndpoint(
146
+ filt, inst.process, requiredPermissions=frozenset()
147
+ )
147
148
 
148
149
  self._processEnvelope(payload)
149
150
  yield self._flushReactor()
@@ -57,9 +57,9 @@ class PayloadEnvelope(Jsonable):
57
57
  ) -> None:
58
58
  Jsonable.__init__(self)
59
59
  """Constructor"""
60
- assert not (data and encodedPayload), (
61
- "You can not pass encodedData and encodedPayload"
62
- )
60
+ assert not (
61
+ data and encodedPayload
62
+ ), "You can not pass encodedData and encodedPayload"
63
63
 
64
64
  self.filt: Dict = {} if filt is None else filt
65
65
  self.data: Union[Any, str, None] = data if data else encodedPayload
@@ -75,18 +75,18 @@ class PayloadEnvelope(Jsonable):
75
75
  @property
76
76
  def encodedPayload(self) -> Optional[str]:
77
77
  # self.data could be from VortexJS/Typescript where we allow `str` data
78
- assert self.data is None or isinstance(self.data, str), (
79
- "Encoded payload is not None or a string"
80
- )
78
+ assert self.data is None or isinstance(
79
+ self.data, str
80
+ ), "Encoded payload is not None or a string"
81
81
  return self.data
82
82
 
83
83
  @encodedPayload.setter
84
84
  def encodedPayload(self, val: Optional[str]):
85
85
  # This value should only be set from users of VortexPy and
86
86
  # Payload.toEncodedPayload so we only allow str or None
87
- assert val is None or isinstance(val, str), (
88
- "Encoded payload is not None or a string"
89
- )
87
+ assert val is None or isinstance(
88
+ val, str
89
+ ), "Encoded payload is not None or a string"
90
90
  self.data = val
91
91
 
92
92
  def isEmpty(self):
@@ -8,6 +8,7 @@
8
8
 
9
9
  import logging
10
10
  from datetime import datetime
11
+ from typing import Optional
11
12
 
12
13
  import pytz
13
14
  from twisted.internet import reactor
@@ -59,8 +60,8 @@ class PayloadIO(object):
59
60
  payloadEnvelope: PayloadEnvelope,
60
61
  vortexUuid: str,
61
62
  vortexName: str,
62
- vortexSession,
63
- sendResponse: SendVortexMsgResponseCallable,
63
+ vortexSession=None,
64
+ sendResponse: Optional[SendVortexMsgResponseCallable] = None,
64
65
  ):
65
66
  from vortex.VortexFactory import VortexFactory
66
67
 
@@ -95,8 +96,8 @@ class PayloadIO(object):
95
96
  payloadEnvelope: PayloadEnvelope,
96
97
  vortexUuid: str,
97
98
  vortexName: str,
98
- vortexSession,
99
- sendResponse: SendVortexMsgResponseCallable,
99
+ vortexSession=None,
100
+ sendResponse: Optional[SendVortexMsgResponseCallable] = None,
100
101
  ):
101
102
  startDate = datetime.now(pytz.utc)
102
103
 
@@ -106,6 +107,10 @@ class PayloadIO(object):
106
107
  common function
107
108
  """
108
109
  try:
110
+ assert sendResponse is not None, (
111
+ "PayloadIO._processLater: sendResponse must be set"
112
+ " to respond to an exception"
113
+ )
109
114
  sendResponse(
110
115
  PayloadEnvelope(
111
116
  filt=payloadEnvelope.filt,
@@ -115,15 +120,8 @@ class PayloadIO(object):
115
120
  except Exception as e:
116
121
  logger.exception(e)
117
122
 
118
- if endpoint._logProcessException:
119
- vortexLogFailure(failure, logger)
120
- logger.error(payloadEnvelope.filt)
121
- else:
122
- logger.debug(
123
- "Payload handler response for filt=%s: %s",
124
- payloadEnvelope.filt,
125
- failure.getErrorMessage(),
126
- )
123
+ vortexLogFailure(failure, logger)
124
+ logger.error(payloadEnvelope.filt)
127
125
 
128
126
  def callback(value, blocking=False):
129
127
  # A blocking call taking more than 100ms is BAD
@@ -7,4 +7,5 @@
7
7
  # Support : contact@flyingion.com
8
8
 
9
9
  RPC_PRIORITY = 10
10
+ HEARTBEAT_PRIORITY = 20
10
11
  DEFAULT_PRIORITY = 100
@@ -67,7 +67,7 @@ class PayloadResponse(Deferred):
67
67
  destVortexUuid: Optional[str] = None,
68
68
  timeout: Optional[float] = None,
69
69
  resultCheck=True,
70
- logTimeoutException=True,
70
+ logTimeoutError=True,
71
71
  ) -> None:
72
72
  """Constructor
73
73
 
@@ -81,8 +81,6 @@ class PayloadResponse(Deferred):
81
81
  :param timeout The timeout to wait for a response
82
82
  :param resultCheck Should the response payload.result be checked, if it fails
83
83
  it errback will be called.
84
- :param logTimeoutException: When True, payload timeouts log at ERROR.
85
- When False, timeouts log at DEBUG with neutral wording.
86
84
  """
87
85
  Deferred.__init__(self)
88
86
 
@@ -90,7 +88,7 @@ class PayloadResponse(Deferred):
90
88
  timeout = self.TIMEOUT
91
89
 
92
90
  self._resultCheck = resultCheck
93
- self._logTimeoutException = logTimeoutException
91
+ self._logTimeoutError = logTimeoutError
94
92
 
95
93
  # uuid4 can have collisions
96
94
  self._messageId = str(uuid4()) + str(PayloadResponse.__SEQ)
@@ -144,10 +142,8 @@ class PayloadResponse(Deferred):
144
142
  self._endpoint.shutdown()
145
143
  self._endpoint = None
146
144
 
147
- if self._logTimeoutException:
145
+ if self._logTimeoutError:
148
146
  logger.error("Timed out for payload %s", self._filt)
149
- else:
150
- logger.debug("Payload response timed out for %s", self._filt)
151
147
  self._status = self.TIMED_OUT
152
148
  return failure
153
149
 
@@ -157,7 +153,6 @@ class PayloadResponse(Deferred):
157
153
  vortexUuid: str,
158
154
  vortexName: str,
159
155
  sendResponse=None,
160
- vortexSession=None,
161
156
  ):
162
157
  if self._endpoint:
163
158
  self._endpoint.shutdown()
@@ -7,11 +7,10 @@
7
7
  # Support : contact@flyingion.com
8
8
 
9
9
  import decimal
10
- import enum
11
10
  import logging
12
11
  from base64 import b64encode, b64decode
13
12
  from collections import defaultdict
14
- from datetime import UTC, datetime, date
13
+ from datetime import datetime, date
15
14
 
16
15
  import pytz
17
16
  from sqlalchemy.orm.collections import InstrumentedList
@@ -124,35 +123,6 @@ def deserialiseWkb(wkbB64Data: str) -> WKBElement:
124
123
  return from_shape(shapely.wkb.loads(b64decode(wkbB64Data)))
125
124
 
126
125
 
127
- def requireAwareDatetime(value: datetime) -> datetime:
128
- """Require a timezone-aware datetime for BSON serialisation."""
129
- if value.tzinfo is None:
130
- raise TypeError(
131
- "datetime must be timezone-aware for BSON serialisation,"
132
- f" got naive {value!r}"
133
- )
134
- return value
135
-
136
-
137
- def datetimeToBson(value: datetime) -> int:
138
- """Serialise a datetime to UTC epoch milliseconds for BSON storage."""
139
- aware = requireAwareDatetime(value)
140
- return int(aware.timestamp() * 1000)
141
-
142
-
143
- def datetimeFromBson(value: object) -> datetime:
144
- """Deserialise a BSON datetime field from UTC epoch milliseconds."""
145
- if isinstance(value, datetime):
146
- return requireAwareDatetime(value)
147
- if isinstance(value, int) and not isinstance(value, bool):
148
- return datetime.fromtimestamp(value / 1000, tz=UTC)
149
- if isinstance(value, float):
150
- return datetime.fromtimestamp(value / 1000, tz=UTC)
151
- raise TypeError(
152
- f"Expected BSON datetime or epoch milliseconds, got {type(value).__name__}"
153
- )
154
-
155
-
156
126
  TYPE_MAP_RAPUI_TO_PY = {
157
127
  T_FLOAT: float,
158
128
  T_STR: str,
@@ -249,12 +219,13 @@ def fromStr(val: str, typeName: str):
249
219
  )
250
220
 
251
221
  if len(val) <= len("2017-06-06 09:40:37.097068"):
252
- raise ValueError(
253
- f"datetime string must include timezone information, got {val!r}"
222
+ logger.warning(
223
+ "Parsing date time with no timezone: %s Setting TZ to UTC",
224
+ val,
254
225
  )
226
+ return datetime.strptime(val, ISO8601_NOTZ).replace(tzinfo=pytz.UTC)
255
227
 
256
- parsed = datetime.strptime(val, ISO8601)
257
- return requireAwareDatetime(parsed)
228
+ return datetime.strptime(val, ISO8601)
258
229
 
259
230
  if typeName == T_BOOL:
260
231
  return val == V_TRUE
@@ -296,13 +267,6 @@ def toRapuiType(value):
296
267
  if isinstance(value, PayloadEnvelope):
297
268
  return T_RAPUI_PAYLOAD_ENVELOPE
298
269
 
299
- if isinstance(value, enum.Enum):
300
- if isinstance(value, str):
301
- return T_STR
302
- if isinstance(value, int):
303
- return T_INT
304
- raise KeyError(value.__class__)
305
-
306
270
  valueClass = value.__class__
307
271
 
308
272
  return TYPE_MAP_PY_TO_RAPUI[valueClass]
@@ -281,6 +281,12 @@ class TupleField:
281
281
  class _Map:
282
282
  pass
283
283
 
284
+ def __new__(cls, *args: Any, **kwargs: Any) -> Any:
285
+ # Tuple classes declare fields as `field: str = TupleField()`.
286
+ # The Any return keeps those declarations type-correct without
287
+ # per-field type-ignore comments. Runtime behaviour is unchanged.
288
+ return super().__new__(cls)
289
+
284
290
  def __init__(
285
291
  self,
286
292
  defaultValue=None,
@@ -354,12 +360,13 @@ class IntTupleFieldValidator(TupleFieldValidatorABC):
354
360
  def validate(self, fieldName: str, value):
355
361
  if self._low is not None and value < self._low:
356
362
  raise ValueError(
357
- f"Field {fieldName}, Value {value} is less than {self._low}"
363
+ f"Field {fieldName}," f" Value {value} is less than {self._low}"
358
364
  )
359
365
 
360
366
  if self._high is not None and self._high < value:
361
367
  raise ValueError(
362
- f"Field {fieldName}, Value {value} is greater than {self._high}"
368
+ f"Field {fieldName},"
369
+ f" Value {value} is greater than {self._high}"
363
370
  )
364
371
 
365
372
 
@@ -383,13 +390,6 @@ class _TupleToPlainJsonMixin:
383
390
  self, includeNones, includeFalse
384
391
  )
385
392
 
386
- def tupleToBsonJsonDict(self, includeNones=True, includeFalse=True):
387
- from vortex.TupleBson import TupleBsonConverter
388
-
389
- return TupleBsonConverter.tupleToBsonJsonDict(
390
- self, includeNones, includeFalse
391
- )
392
-
393
393
  def __tupleToJsonDict(
394
394
  self, includeNones=True, includeFalse=True, useShortNames=True
395
395
  ):
@@ -513,31 +513,6 @@ class _TupleToPlainJsonMixin:
513
513
  jsonDict, TupleClass
514
514
  )
515
515
 
516
- @staticmethod
517
- def bsonJsonDictToTupleWithValidation(
518
- jsonDict: dict, TupleClass: Type["Tuple"]
519
- ) -> "Tuple":
520
- """BSON document dict to Tuple with validation.
521
-
522
- This method uses the typings on the fields in a tuple to validate the
523
- types of data.
524
- """
525
- from vortex.TupleBson import TupleBsonConverter
526
-
527
- return TupleBsonConverter.bsonJsonDictToTupleWithValidation(
528
- jsonDict, TupleClass
529
- )
530
-
531
- @staticmethod
532
- def bsonJsonDictToTupleWithValidationResult(
533
- jsonDict: dict, TupleClass: Type["Tuple"]
534
- ) -> "TupleValidationResult":
535
- from vortex.TupleBson import TupleBsonConverter
536
-
537
- return TupleBsonConverter.bsonJsonDictToTupleWithValidationResult(
538
- jsonDict, TupleClass
539
- )
540
-
541
516
 
542
517
  class Tuple(Jsonable, _TupleToPlainJsonMixin, _TupleToSqlaJsonMixin):
543
518
  """Tuple
@@ -98,8 +98,5 @@ class PolymorphicTupleTypeFieldArgTest(unittest.TestCase):
98
98
  any(err.field_path == "concreteType" for err in result.errors)
99
99
  )
100
100
  self.assertTrue(
101
- any(
102
- err.message.startswith("At concreteType:")
103
- for err in result.errors
104
- )
101
+ any(err.message.startswith("At concreteType:") for err in result.errors)
105
102
  )