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.
- vortexpy-6.0.0/LICENSE +22 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/PKG-INFO +10 -7
- {vortexpy-4.3.5 → vortexpy-6.0.0}/README.md +9 -6
- {vortexpy-4.3.5 → vortexpy-6.0.0}/pyproject.toml +1 -11
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/Jsonable.py +2 -4
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadEndpoint.py +31 -39
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadEndpointTest.py +3 -2
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadEnvelope.py +9 -9
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadIO.py +11 -13
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadPriority.py +1 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadResponse.py +3 -8
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/SerialiseUtil.py +6 -42
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/Tuple.py +9 -34
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/TuplePolymorphicTupleTypeFieldArgTest.py +1 -4
- vortexpy-4.3.5/vortex/TupleDocumentConverter.py → vortexpy-6.0.0/vortex/TupleRestful.py +89 -273
- vortexpy-6.0.0/vortex/TupleRestfulTest.py +267 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexAccessControlContextABC.py +47 -13
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexAuthenticatorABC.py +6 -1
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexFactory.py +1 -9
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexPayloadProtocol.py +4 -9
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexPermission.py +0 -1
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexPermissionError.py +0 -1
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexServer.py +18 -1
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexServerConnection.py +2 -2
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexServerWebsocket.py +4 -2
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexSession.py +65 -14
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexTcpMemoryLeakPayloadTest.py +24 -24
- vortexpy-6.0.0/vortex/VortexUserDetailsABC.py +40 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexWritePushProducer.py +53 -26
- vortexpy-6.0.0/vortex/VortexWritePushProducer_test.py +75 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/__init__.py +1 -1
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/data_loader/TupleDataLiveLockManager.py +147 -65
- vortexpy-6.0.0/vortex/data_loader/TupleDataLiveLockManager_test.py +336 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/data_loader/TupleDataLoader.py +16 -28
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/data_loader/TupleDataLoaderDelegate.py +4 -14
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/data_loader/TupleDataLoaderTuples.py +13 -9
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/event_bus/TupleChangeEventBus.py +8 -7
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleActionProcessor.py +87 -103
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleActionProcessorProxy.py +13 -13
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleActionProcessorTestHandler.py +3 -3
- vortexpy-6.0.0/vortex/handler/TupleActionProcessor_test.py +147 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleDataAccessGranterABC.py +4 -4
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleDataObservableHandler.py +99 -101
- vortexpy-6.0.0/vortex/handler/TupleDataObservableHandler_test.py +280 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleDataObservableProxyHandler.py +37 -39
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleDataObserverClient.py +2 -3
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleDataProviderABC.py +1 -3
- vortexpy-6.0.0/vortex/restful/RestfulHttpClientTest.py +68 -0
- vortexpy-6.0.0/vortex/restful/RestfulResourceTest.py +108 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/rpc/RPC.py +44 -73
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/sqla_orm/OrmCrudHandler.py +34 -8
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/storage/TupleStorageSqlite.py +3 -3
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/tuple_json_schema/tuple_json_schema_builder.py +1 -3
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/tuple_json_schema/tuple_json_schema_test.py +2 -4
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/twisted_runtime_metrics.py +48 -91
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/twisted_runtime_metrics_test.py +7 -7
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortexpy.egg-info/PKG-INFO +10 -7
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortexpy.egg-info/SOURCES.txt +8 -11
- vortexpy-4.3.5/vortex/PayloadResponse_test.py +0 -42
- vortexpy-4.3.5/vortex/SerialiseUtil_test.py +0 -123
- vortexpy-4.3.5/vortex/TupleBson.py +0 -144
- vortexpy-4.3.5/vortex/TupleBson_test.py +0 -221
- vortexpy-4.3.5/vortex/TupleRestful.py +0 -134
- vortexpy-4.3.5/vortex/TupleRestful_test.py +0 -209
- vortexpy-4.3.5/vortex/handler/TupleDataObservableHandler_test.py +0 -264
- vortexpy-4.3.5/vortex/payload_many_tuples_benchmark_test.py +0 -71
- vortexpy-4.3.5/vortex/restful/restful_http_client_test.py +0 -73
- vortexpy-4.3.5/vortex/restful/restful_resource_test.py +0 -76
- vortexpy-4.3.5/vortex/rpc/RPC_test.py +0 -114
- vortexpy-4.3.5/vortex/tuple_test_fixtures.py +0 -184
- {vortexpy-4.3.5 → vortexpy-6.0.0}/setup.cfg +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/DataWrapTuple.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/DeferUtil.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/DeferUtil_test.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/MtlsAuthorizedClientRegistry.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/MtlsAuthorizedClientRegistry_test.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/MtlsServerSslOptions.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/Payload.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadFilterKeys.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadResponseTest.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PayloadTest.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/PerformTestActionTuple.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/TestTuple.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/TupleAction.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/TupleDataForTest.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/TuplePerfTest.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/TupleSelector.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/TupleTest.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexABC.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexClientHttp.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexClientHttpTest.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexClientTcp.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexClientWebsocketFactory.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexConnectionABC.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexEndpointPermission.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexFactory_test.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexServerConnection_test.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexServerHttpResource.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexServerTcp.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexSession_test.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexTcpMemoryLeakRawTest.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexTest.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/VortexUtil.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/data_loader/TupleDataLoaderTupleABC.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/data_loader/__init__.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/event_bus/TupleChangeEventABC.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/event_bus/TupleChangeEventBusObserverABC.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/event_bus/__init__.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleActionProcessor_shutdown_test.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleActionPushOffline.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleActionPushOffline_test.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleDataActionClient.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleDataObservableCache.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/TupleDataObservableTestHandler.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/VortexJSTupleLoaderTestHandler.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/handler/__init__.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/mypy_plugin.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/py.typed +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/restful/GzippedDataHttpClient.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/restful/GzippedDataHttpClientTest.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/restful/GzippedPayloadHttpClient.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/restful/RestfulHttpClient.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/restful/RestfulResource.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/restful/__init__.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/rpc/RPCTest.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/rpc/__init__.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/sqla_orm/TupleProviderForDb.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/sqla_orm/VortexTupleJson.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/sqla_orm/__init__.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/storage/TupleActionStorageSqlite.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/storage/__init__.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/tuple_json_schema/__init__.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/tuple_json_schema/constants.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/tuple_json_schema/json_schema_node_tuple.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortex/tuple_json_schema/tuple_json_schema_test_tuples.py +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortexpy.egg-info/dependency_links.txt +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortexpy.egg-info/entry_points.txt +0 -0
- {vortexpy-4.3.5 → vortexpy-6.0.0}/vortexpy.egg-info/requires.txt +0 -0
- {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:
|
|
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
|
|
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.
|
|
44
|
-
* Twisted
|
|
45
|
-
* SQLAlchemy
|
|
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
|
|
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.
|
|
15
|
-
* Twisted
|
|
16
|
-
* SQLAlchemy
|
|
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 = "
|
|
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
|
-
"%
|
|
113
|
-
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
61
|
-
|
|
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(
|
|
79
|
-
|
|
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(
|
|
88
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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
|
|
@@ -67,7 +67,7 @@ class PayloadResponse(Deferred):
|
|
|
67
67
|
destVortexUuid: Optional[str] = None,
|
|
68
68
|
timeout: Optional[float] = None,
|
|
69
69
|
resultCheck=True,
|
|
70
|
-
|
|
70
|
+
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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},
|
|
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
|
)
|