vortexpy 4.0.25__tar.gz → 4.0.28__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vortexpy-4.0.25 → vortexpy-4.0.28}/LICENSE +2 -1
- vortexpy-4.0.28/PKG-INFO +186 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/README.md +9 -2
- vortexpy-4.0.28/pyproject.toml +55 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/DataWrapTuple.py +8 -8
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/DeferUtil.py +26 -11
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/Jsonable.py +7 -8
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/Payload.py +8 -8
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/PayloadEndpoint.py +107 -21
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/PayloadEndpointTest.py +15 -16
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/PayloadEnvelope.py +7 -8
- vortexpy-4.0.28/vortex/PayloadFilterKeys.py +14 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/PayloadIO.py +12 -12
- vortexpy-4.0.28/vortex/PayloadPriority.py +10 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/PayloadResponse.py +11 -1
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/PayloadResponseTest.py +8 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/PayloadTest.py +8 -8
- vortexpy-4.0.28/vortex/PerformTestActionTuple.py +23 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/SerialiseUtil.py +7 -8
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/TestTuple.py +10 -16
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/Tuple.py +28 -266
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/TupleAction.py +8 -8
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/TupleDataForTest.py +8 -8
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/TuplePerfTest.py +8 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/TuplePolymorphicTupleTypeFieldArgTest.py +31 -3
- vortexpy-4.0.28/vortex/TupleRestful.py +563 -0
- vortexpy-4.0.28/vortex/TupleRestfulTest.py +267 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/TupleSelector.py +8 -8
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/TupleTest.py +7 -8
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexABC.py +8 -0
- vortexpy-4.0.28/vortex/VortexAccessControlContextABC.py +110 -0
- vortexpy-4.0.28/vortex/VortexAuthenticatorABC.py +152 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexClientHttp.py +8 -8
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexClientHttpTest.py +8 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexClientTcp.py +8 -8
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexClientWebsocketFactory.py +7 -8
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexConnectionABC.py +7 -8
- vortexpy-4.0.28/vortex/VortexEndpointPermission.py +40 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexFactory.py +30 -2
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexPayloadProtocol.py +25 -24
- vortexpy-4.0.28/vortex/VortexPermission.py +17 -0
- vortexpy-4.0.28/vortex/VortexPermissionError.py +40 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexServer.py +11 -10
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexServerConnection.py +8 -8
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexServerHttpResource.py +20 -8
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexServerTcp.py +37 -8
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexServerWebsocket.py +28 -8
- vortexpy-4.0.28/vortex/VortexSession.py +220 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexTcpMemoryLeakPayloadTest.py +11 -9
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexTcpMemoryLeakRawTest.py +8 -8
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexTest.py +7 -8
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexUtil.py +8 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/VortexWritePushProducer.py +8 -8
- vortexpy-4.0.28/vortex/__init__.py +9 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/data_loader/TupleDataLiveLockManager.py +12 -5
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/data_loader/TupleDataLoader.py +96 -31
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/data_loader/TupleDataLoaderDelegate.py +31 -12
- vortexpy-4.0.28/vortex/data_loader/TupleDataLoaderTupleABC.py +18 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/data_loader/TupleDataLoaderTuples.py +8 -0
- vortexpy-4.0.28/vortex/data_loader/__init__.py +8 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/event_bus/TupleChangeEventABC.py +8 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/event_bus/TupleChangeEventBus.py +8 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/event_bus/TupleChangeEventBusObserverABC.py +8 -0
- vortexpy-4.0.28/vortex/event_bus/__init__.py +8 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/handler/TupleActionProcessor.py +143 -40
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/handler/TupleActionProcessorProxy.py +10 -1
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/handler/TupleActionProcessorTestHandler.py +8 -0
- vortexpy-4.0.28/vortex/handler/TupleDataAccessGranterABC.py +48 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/handler/TupleDataActionClient.py +8 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/handler/TupleDataObservableCache.py +18 -1
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/handler/TupleDataObservableHandler.py +138 -22
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/handler/TupleDataObservableProxyHandler.py +124 -10
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/handler/TupleDataObservableTestHandler.py +18 -6
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/handler/TupleDataObserverClient.py +9 -0
- vortexpy-4.0.28/vortex/handler/TupleDataProviderABC.py +39 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/handler/VortexJSTupleLoaderTestHandler.py +8 -0
- vortexpy-4.0.28/vortex/handler/__init__.py +8 -0
- vortexpy-4.0.28/vortex/mypy_plugin.py +57 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/restful/GzippedDataHttpClient.py +8 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/restful/GzippedDataHttpClientTest.py +8 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/restful/GzippedPayloadHttpClient.py +9 -1
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/restful/RestfulHttpClient.py +9 -1
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/restful/RestfulHttpClientTest.py +8 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/restful/RestfulResource.py +8 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/restful/RestfulResourceTest.py +8 -0
- vortexpy-4.0.28/vortex/restful/__init__.py +8 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/rpc/RPC.py +42 -4
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/rpc/RPCTest.py +8 -0
- vortexpy-4.0.28/vortex/rpc/__init__.py +8 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/sqla_orm/OrmCrudHandler.py +11 -9
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/sqla_orm/TupleProviderForDb.py +17 -3
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/sqla_orm/VortexTupleJson.py +8 -0
- vortexpy-4.0.28/vortex/sqla_orm/__init__.py +8 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortex/storage/TupleStorageSqlite.py +8 -0
- vortexpy-4.0.28/vortex/storage/__init__.py +8 -0
- vortexpy-4.0.28/vortexpy.egg-info/PKG-INFO +186 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortexpy.egg-info/SOURCES.txt +12 -1
- vortexpy-4.0.28/vortexpy.egg-info/entry_points.txt +2 -0
- vortexpy-4.0.25/PKG-INFO +0 -44
- vortexpy-4.0.25/pyproject.toml +0 -45
- vortexpy-4.0.25/vortex/PayloadFilterKeys.py +0 -15
- vortexpy-4.0.25/vortex/PayloadPriority.py +0 -11
- vortexpy-4.0.25/vortex/PerformTestActionTuple.py +0 -30
- vortexpy-4.0.25/vortex/TupleActionVortex.py +0 -5
- vortexpy-4.0.25/vortex/TupleRestfulTest.py +0 -140
- vortexpy-4.0.25/vortex/__init__.py +0 -10
- vortexpy-4.0.25/vortex/data_loader/TupleDataLoaderTupleABC.py +0 -10
- vortexpy-4.0.25/vortex/event_bus/__init__.py +0 -0
- vortexpy-4.0.25/vortex/handler/__init__.py +0 -0
- vortexpy-4.0.25/vortex/restful/__init__.py +0 -0
- vortexpy-4.0.25/vortex/rpc/__init__.py +0 -0
- vortexpy-4.0.25/vortex/sqla_orm/__init__.py +0 -0
- vortexpy-4.0.25/vortex/storage/__init__.py +0 -0
- vortexpy-4.0.25/vortexpy.egg-info/PKG-INFO +0 -44
- {vortexpy-4.0.25 → vortexpy-4.0.28}/setup.cfg +0 -0
- /vortexpy-4.0.25/vortex/data_loader/__init__.py → /vortexpy-4.0.28/vortex/py.typed +0 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortexpy.egg-info/dependency_links.txt +0 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortexpy.egg-info/requires.txt +0 -0
- {vortexpy-4.0.25 → vortexpy-4.0.28}/vortexpy.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2016
|
|
3
|
+
Copyright (c) 2016-2026 Flying Ion Pty Ltd
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -19,3 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
SOFTWARE.
|
|
22
|
+
|
vortexpy-4.0.28/PKG-INFO
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vortexpy
|
|
3
|
+
Version: 4.0.28
|
|
4
|
+
Summary: Flying Ion's observable, routable, data serialisation and transport code.
|
|
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
|
+
|
|
29
|
+
Project-URL: Homepage, https://flyingion.com
|
|
30
|
+
Project-URL: Repository, https://gitlab.com/flyingion/vortexpy
|
|
31
|
+
Project-URL: Issues, https://gitlab.com/flyingion/vortexpy/-/issues
|
|
32
|
+
Keywords: vortex,observable,http,compressed,flying-ion
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
Requires-Dist: SQLAlchemy>=2.0
|
|
41
|
+
Requires-Dist: GeoAlchemy2
|
|
42
|
+
Requires-Dist: txwebsocket>=1.1.0
|
|
43
|
+
Requires-Dist: autobahn[twisted]
|
|
44
|
+
Requires-Dist: vcversioner
|
|
45
|
+
Requires-Dist: six
|
|
46
|
+
Requires-Dist: pytz
|
|
47
|
+
Requires-Dist: reactivex>=4.0.4
|
|
48
|
+
Requires-Dist: ddt>=1.6.0
|
|
49
|
+
Requires-Dist: psutil
|
|
50
|
+
Requires-Dist: txhttputil>=1.2.0
|
|
51
|
+
|
|
52
|
+
# VortexPY
|
|
53
|
+
|
|
54
|
+
Flying Ion's observable, routable, data serialisation and transport code.
|
|
55
|
+
|
|
56
|
+
See [README_PERMISSIONS.md](README_PERMISSIONS.md) for the two-level permission
|
|
57
|
+
system (endpoint guards + access control context) introduced in v5.0.
|
|
58
|
+
|
|
59
|
+
Why PY at the end? To be consistent with VortexJS, which is the Browser side package.
|
|
60
|
+
See https://gitlab.com/flyingion/vortexjs
|
|
61
|
+
|
|
62
|
+
Requirements:
|
|
63
|
+
* Python 3.5
|
|
64
|
+
* Twisted 16+
|
|
65
|
+
* SQLAlchemy 1, 1.1
|
|
66
|
+
|
|
67
|
+
The "vortex" is designed to transport "Payloads" from a web browser (VortexJS) to a
|
|
68
|
+
twisted web server (VortexPY). There is also a python client for python client to python server
|
|
69
|
+
communication.
|
|
70
|
+
|
|
71
|
+
### Vortex, The Observable Part
|
|
72
|
+
|
|
73
|
+
Vortex was designed to allow the Python server to send updates to the browser with out
|
|
74
|
+
the browser having to create HTTP request polls.
|
|
75
|
+
|
|
76
|
+
This is achieved by the browser/client sending one or more payloads to the server as a
|
|
77
|
+
HTTP POST, the server then uses this connection to send payloads back to the client.
|
|
78
|
+
|
|
79
|
+
This HTTP POST connection remains active, Until the client needs
|
|
80
|
+
to send more Payloads to the server. To do this, the client simply starts a new HTTP POST
|
|
81
|
+
request to the server, the server will then close the old request and begin sending
|
|
82
|
+
Payloads down the respose path of the new request.
|
|
83
|
+
|
|
84
|
+
Python classes = Vortex
|
|
85
|
+
|
|
86
|
+
### Transport
|
|
87
|
+
|
|
88
|
+
Transport occurs over a HTTP(S) layer.
|
|
89
|
+
|
|
90
|
+
Experimental classes are present to transport data to/from subprocess pipes, this
|
|
91
|
+
worked, except the subprocess didn't die and the idea was abandoned
|
|
92
|
+
(It was a Celery worker).
|
|
93
|
+
|
|
94
|
+
Because of this effort, it's much easier to add other transport mechanisms.
|
|
95
|
+
|
|
96
|
+
Python classes = VortexResource (Receive), VortexConnection (Send)
|
|
97
|
+
|
|
98
|
+
### Payloads
|
|
99
|
+
A "Payload" is a class that stores some routing information, a result and a list of
|
|
100
|
+
"Tuples".
|
|
101
|
+
|
|
102
|
+
The routing information is a dict / json object which looks like this
|
|
103
|
+
{key:"anything.type.of.data.list"}
|
|
104
|
+
(See Routable for more info)
|
|
105
|
+
|
|
106
|
+
The tuples attribute stores the data, typically this is a list of Tuple (see Tuples)
|
|
107
|
+
objects, however, it can be any data structure that JSON can serialise,
|
|
108
|
+
plus Date()/datetime.
|
|
109
|
+
|
|
110
|
+
The Payload class is responsible for serialising and compressing it's data down to a
|
|
111
|
+
"Vortex Msg", ready for transport. Compression is done with zlib, this imrpovement sees
|
|
112
|
+
data reduced to 10% of the original size.
|
|
113
|
+
|
|
114
|
+
There is custom code that handles converting the structure to JSON objects for json.dumps
|
|
115
|
+
so support can be (carefully) added for other data types such as bytes.
|
|
116
|
+
|
|
117
|
+
Python classes = Payload
|
|
118
|
+
|
|
119
|
+
### Tuples
|
|
120
|
+
|
|
121
|
+
Not to be confused with the built in Python tuple syntax (1, 2, 3), Tuples are registered
|
|
122
|
+
classes with registered attributes.
|
|
123
|
+
|
|
124
|
+
Vortex Tuples are important in the design as classes that inherit the vortex Tuple
|
|
125
|
+
class will reconstructed as the proper classes at the other end in python. They will have
|
|
126
|
+
onlt the fields registerd as TupleFields populated when they are reconstructed.
|
|
127
|
+
|
|
128
|
+
This is especially useful when using the SQLAlchemy ORM, as the Tuple code recognises
|
|
129
|
+
column definitions as TupleFields.
|
|
130
|
+
|
|
131
|
+
You can just assign session.query(MyTable).all() to payload.tuples, and the vortex will
|
|
132
|
+
know how to serialise.
|
|
133
|
+
|
|
134
|
+
From here you can send it do the browser, where the browser may edit some attributes of
|
|
135
|
+
the tuples, then send it back to the python server to update the database.
|
|
136
|
+
(More on this in Handlers)
|
|
137
|
+
|
|
138
|
+
Python classes = Tuple, TupleField
|
|
139
|
+
|
|
140
|
+
### Routable
|
|
141
|
+
|
|
142
|
+
When a vortex receives a payload, it is sent to the PayloadIO
|
|
143
|
+
|
|
144
|
+
The PayloadIO, then passes it on to each PayloadEndpoint that has registered with it.
|
|
145
|
+
|
|
146
|
+
Payload routing ends with PayloadEndpoints, These endpoints are constructed with another
|
|
147
|
+
filter dict/json like object, and a callback.
|
|
148
|
+
|
|
149
|
+
When PayloadIO tells a PaylaodEndpoint that
|
|
150
|
+
a new Payload has arrived, the PayloadEndpoint checks the Payload.filt against it's own
|
|
151
|
+
filt.
|
|
152
|
+
|
|
153
|
+
If the PayloadEndpoints filt is a subset of the Payload.filt, the PayloadEndpoint then
|
|
154
|
+
calls it's callable and passes it the Payload.
|
|
155
|
+
|
|
156
|
+
PayloadEndpoints register them selves with PayloadIO on construction.
|
|
157
|
+
PayloadIO keeps a list of weak refs to the PayloadEndpoints.
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
Payloads contain a "filt" attribute, which is a json like dict/object.
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
Python classes = PayloadEndpoint, PayloadIO
|
|
166
|
+
|
|
167
|
+
### Handlers
|
|
168
|
+
|
|
169
|
+
Handlers are the provided classes that handle data from the PayloadEndpoints.
|
|
170
|
+
|
|
171
|
+
* ModelHandler : Simply specify the payload filter and implement the "buildModel" method.
|
|
172
|
+
* OrmCrudHandler : This handler used to Create, Update, Retrieve and Delete SQLAlchemy data.
|
|
173
|
+
It can handle an array of tuples, of different types, and has various
|
|
174
|
+
hooks allowing the data to be customised before being stored or retreived.
|
|
175
|
+
* AsyncModelHandler : Experimental handler that will likely be deleted.
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
#Change Log
|
|
179
|
+
|
|
180
|
+
#### 0.3.0
|
|
181
|
+
|
|
182
|
+
Implemented PY side WebSockets
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
Copyright (c) 2016-2026 [Flying Ion Pty Ltd](https://flyingion.com). Released under the [MIT License](LICENSE).
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# VortexPY
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Flying Ion's observable, routable, data serialisation and transport code.
|
|
4
|
+
|
|
5
|
+
See [README_PERMISSIONS.md](README_PERMISSIONS.md) for the two-level permission
|
|
6
|
+
system (endpoint guards + access control context) introduced in v5.0.
|
|
4
7
|
|
|
5
8
|
Why PY at the end? To be consistent with VortexJS, which is the Browser side package.
|
|
6
|
-
See https://
|
|
9
|
+
See https://gitlab.com/flyingion/vortexjs
|
|
7
10
|
|
|
8
11
|
Requirements:
|
|
9
12
|
* Python 3.5
|
|
@@ -126,3 +129,7 @@ Handlers are the provided classes that handle data from the PayloadEndpoints.
|
|
|
126
129
|
#### 0.3.0
|
|
127
130
|
|
|
128
131
|
Implemented PY side WebSockets
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
Copyright (c) 2016-2026 [Flying Ion Pty Ltd](https://flyingion.com). Released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.2"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vortexpy"
|
|
7
|
+
version = "4.0.28"
|
|
8
|
+
license = {file = "LICENSE"}
|
|
9
|
+
authors = [{name = "Flying Ion Pty Ltd", email = "contact@flyingion.com"}]
|
|
10
|
+
description = "Flying Ion's observable, routable, data serialisation and transport code."
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
keywords = [
|
|
13
|
+
"vortex",
|
|
14
|
+
"observable",
|
|
15
|
+
"http",
|
|
16
|
+
"compressed",
|
|
17
|
+
"flying-ion",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"SQLAlchemy >= 2.0",
|
|
29
|
+
"GeoAlchemy2",
|
|
30
|
+
"txwebsocket>=1.1.0",
|
|
31
|
+
"autobahn[twisted]",
|
|
32
|
+
"vcversioner",
|
|
33
|
+
"six",
|
|
34
|
+
"pytz",
|
|
35
|
+
"reactivex>=4.0.4",
|
|
36
|
+
"ddt >=1.6.0",
|
|
37
|
+
"psutil",
|
|
38
|
+
"txhttputil>=1.2.0",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://flyingion.com"
|
|
43
|
+
Repository = "https://gitlab.com/flyingion/vortexpy"
|
|
44
|
+
Issues = "https://gitlab.com/flyingion/vortexpy/-/issues"
|
|
45
|
+
|
|
46
|
+
[tool.setuptools]
|
|
47
|
+
include-package-data = false
|
|
48
|
+
license-files = []
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.packages.find]
|
|
51
|
+
exclude = ["test"]
|
|
52
|
+
namespaces = false
|
|
53
|
+
|
|
54
|
+
[project.entry-points."mypy.plugins"]
|
|
55
|
+
vortex_defer = "vortex.mypy_plugin:plugin"
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
9
|
from .Tuple import Tuple, TupleField, addTupleType
|
|
10
10
|
|
|
11
11
|
|
|
@@ -1,5 +1,15 @@
|
|
|
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
|
+
|
|
1
9
|
import inspect
|
|
2
10
|
import logging
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from typing import ParamSpec, TypeVar
|
|
3
13
|
|
|
4
14
|
import twisted
|
|
5
15
|
from twisted.internet import reactor
|
|
@@ -13,6 +23,9 @@ from twisted.internet.threads import deferToThread
|
|
|
13
23
|
|
|
14
24
|
logger = logging.getLogger(__name__)
|
|
15
25
|
|
|
26
|
+
_P = ParamSpec("_P")
|
|
27
|
+
_R = TypeVar("_R")
|
|
28
|
+
|
|
16
29
|
|
|
17
30
|
def callMethodLater(func):
|
|
18
31
|
def wrapper(*args, **kwargs):
|
|
@@ -136,19 +149,21 @@ class YieldInDeferToThreadException(Exception):
|
|
|
136
149
|
|
|
137
150
|
|
|
138
151
|
def deferToThreadWrapWithLogger(
|
|
139
|
-
|
|
140
|
-
|
|
152
|
+
loggerArg: logging.Logger,
|
|
153
|
+
consumeError: bool = False,
|
|
154
|
+
checkMainThread: bool = True,
|
|
155
|
+
) -> Callable[[Callable[_P, _R]], Callable[_P, Deferred[_R]]]:
|
|
141
156
|
"""Defer To Thread Wrap With Logger
|
|
142
157
|
|
|
143
158
|
This method is a decorator used to send blocking methods to threads easily.
|
|
144
159
|
|
|
145
|
-
:param
|
|
160
|
+
:param loggerArg: An instance from logging.getLogger(..)
|
|
146
161
|
:param consumeError: Should errors occuring in the thread be consumed or errback'd
|
|
147
162
|
:param checkMainThread: Don't use this parameter, it's only needed during reactor
|
|
148
163
|
startup when the reactors thread isn't properly set and the check
|
|
149
164
|
fails.
|
|
150
165
|
|
|
151
|
-
:return: A
|
|
166
|
+
:return: A decorator that makes the wrapped callable return a Deferred.
|
|
152
167
|
|
|
153
168
|
Usage: ::
|
|
154
169
|
|
|
@@ -161,7 +176,7 @@ def deferToThreadWrapWithLogger(
|
|
|
161
176
|
|
|
162
177
|
"""
|
|
163
178
|
assert isinstance(
|
|
164
|
-
|
|
179
|
+
loggerArg, logging.Logger
|
|
165
180
|
), """Usage:
|
|
166
181
|
import logging
|
|
167
182
|
logger = logging.getLogger(__name__)
|
|
@@ -170,10 +185,10 @@ def deferToThreadWrapWithLogger(
|
|
|
170
185
|
pass
|
|
171
186
|
"""
|
|
172
187
|
|
|
173
|
-
def wrapper(funcToWrap) -> Deferred:
|
|
188
|
+
def wrapper(funcToWrap: Callable[_P, _R]) -> Callable[_P, Deferred[_R]]:
|
|
174
189
|
def checkForInlineCallbackYield(result):
|
|
175
190
|
if inspect.isgenerator(result):
|
|
176
|
-
|
|
191
|
+
loggerArg.error(
|
|
177
192
|
"Module Name %s, Class Name %s, Function Name %s"
|
|
178
193
|
" has a yield in it. IT SHOULD NOT.",
|
|
179
194
|
funcToWrap.__module__,
|
|
@@ -183,9 +198,9 @@ def deferToThreadWrapWithLogger(
|
|
|
183
198
|
raise YieldInDeferToThreadException()
|
|
184
199
|
return result
|
|
185
200
|
|
|
186
|
-
def func(*args, **kwargs):
|
|
201
|
+
def func(*args: _P.args, **kwargs: _P.kwargs) -> Deferred[_R]:
|
|
187
202
|
if not twisted.python.threadable.isInIOThread() and checkMainThread:
|
|
188
|
-
|
|
203
|
+
loggerArg.error(
|
|
189
204
|
"Module Name %s, Class Name %s, Function Name %s"
|
|
190
205
|
" is being deferred from a thread. IT SHOULD NOT.",
|
|
191
206
|
funcToWrap.__module__,
|
|
@@ -196,9 +211,9 @@ def deferToThreadWrapWithLogger(
|
|
|
196
211
|
"Deferring to a thread can only be done from the main thread"
|
|
197
212
|
)
|
|
198
213
|
|
|
199
|
-
d = deferToThread(funcToWrap, *args, **kwargs)
|
|
214
|
+
d: Deferred[_R] = deferToThread(funcToWrap, *args, **kwargs)
|
|
200
215
|
d.addCallback(checkForInlineCallbackYield)
|
|
201
|
-
d.addErrback(vortexLogFailure,
|
|
216
|
+
d.addErrback(vortexLogFailure, loggerArg, consumeError=consumeError)
|
|
202
217
|
return d
|
|
203
218
|
|
|
204
219
|
return func
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"""
|
|
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
|
|
9
8
|
|
|
10
9
|
import logging
|
|
11
10
|
import traceback
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
9
|
import logging
|
|
10
10
|
import zlib
|
|
11
11
|
from base64 import b64encode, b64decode
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
9
|
import inspect
|
|
10
10
|
import logging
|
|
11
11
|
import types
|
|
@@ -13,9 +13,21 @@ import weakref
|
|
|
13
13
|
from copy import copy
|
|
14
14
|
from typing import Callable, Optional, Dict, Union
|
|
15
15
|
|
|
16
|
+
from twisted.internet.defer import inlineCallbacks, maybeDeferred
|
|
17
|
+
|
|
16
18
|
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
17
19
|
from vortex.PayloadIO import PayloadIO
|
|
18
20
|
from vortex.VortexABC import SendVortexMsgResponseCallable
|
|
21
|
+
from vortex.VortexAccessControlContextABC import (
|
|
22
|
+
NO_AUTH_ACCESS_CONTROL_CONTEXT,
|
|
23
|
+
VortexAccessControlContextABC,
|
|
24
|
+
)
|
|
25
|
+
from vortex.VortexEndpointPermission import EndpointPermissionsType as PermissionsType
|
|
26
|
+
from vortex.VortexSession import (
|
|
27
|
+
_callableLabel,
|
|
28
|
+
checkAccessControlContextSignature,
|
|
29
|
+
checkNoHttpSessionInSignature,
|
|
30
|
+
)
|
|
19
31
|
|
|
20
32
|
logger = logging.getLogger(__name__)
|
|
21
33
|
|
|
@@ -27,7 +39,7 @@ logger = logging.getLogger(__name__)
|
|
|
27
39
|
# # PayloadEnvelope, # payloadEnvelope
|
|
28
40
|
# # str, # vortexUuid
|
|
29
41
|
# # str, # vortexName
|
|
30
|
-
# # Any, #
|
|
42
|
+
# # Any, # vortexSession
|
|
31
43
|
# # SendVortexMsgResponseCallable # sendResponse
|
|
32
44
|
# ],
|
|
33
45
|
# Optional[Deferred] # Return
|
|
@@ -43,19 +55,46 @@ class PayloadEndpoint:
|
|
|
43
55
|
|
|
44
56
|
PERMITTER = None
|
|
45
57
|
|
|
58
|
+
#: Deprecation warning emitted when no requiredPermissions are set.
|
|
59
|
+
_NO_PERMISSIONS_WARNING = (
|
|
60
|
+
"PayloadEndpoint filt=%s has no requiredPermissions set. "
|
|
61
|
+
"In a future release, endpoints without explicit permissions "
|
|
62
|
+
"will raise a RegistrationError."
|
|
63
|
+
)
|
|
64
|
+
|
|
46
65
|
def __init__(
|
|
47
66
|
self,
|
|
48
67
|
filt: Dict,
|
|
49
68
|
callable_,
|
|
50
69
|
acceptOnlyFromVortex: Optional[Union[str, tuple]] = None,
|
|
51
70
|
ignoreFromVortex: Optional[Union[str, tuple]] = None,
|
|
71
|
+
requiredPermissions: Optional[PermissionsType] = None,
|
|
52
72
|
) -> None:
|
|
53
73
|
"""
|
|
54
74
|
:param filt: The filter to match against payloads
|
|
55
75
|
:param callable_: This will be called and passed the payload if it matches
|
|
56
76
|
:param acceptOnlyFromVortex: Accept payloads only from this vortex,
|
|
57
77
|
The vortex can be str or tuple of str, or None to accept from any.
|
|
78
|
+
:param requiredPermissions: Frozenset of
|
|
79
|
+
:class:`~vortex.VortexPermission.VortexPermission` class objects that
|
|
80
|
+
the caller's session must hold. Pass an empty frozenset to
|
|
81
|
+
explicitly declare "no permissions required". Omitting the
|
|
82
|
+
parameter (``None``) emits a deprecation warning.
|
|
58
83
|
"""
|
|
84
|
+
self._requiredPermissions: Optional[PermissionsType] = requiredPermissions
|
|
85
|
+
|
|
86
|
+
if requiredPermissions is None:
|
|
87
|
+
logger.warning(self._NO_PERMISSIONS_WARNING, filt)
|
|
88
|
+
|
|
89
|
+
callableDesc = (
|
|
90
|
+
f"PayloadEndpoint filt={filt} callback={_callableLabel(callable_)}"
|
|
91
|
+
)
|
|
92
|
+
self._callableAcceptsAccessControlContext = checkAccessControlContextSignature(
|
|
93
|
+
callable_,
|
|
94
|
+
callableDescription=callableDesc,
|
|
95
|
+
)
|
|
96
|
+
checkNoHttpSessionInSignature(callable_, callableDesc)
|
|
97
|
+
|
|
59
98
|
self._acceptOnlyFromVortex = acceptOnlyFromVortex
|
|
60
99
|
if isinstance(self._acceptOnlyFromVortex, str):
|
|
61
100
|
self._acceptOnlyFromVortex = (self._acceptOnlyFromVortex,)
|
|
@@ -79,8 +118,8 @@ class PayloadEndpoint:
|
|
|
79
118
|
self._callable = callable_
|
|
80
119
|
self._callable._endpointWeakClass = self
|
|
81
120
|
|
|
82
|
-
def __call__(self,
|
|
83
|
-
self._callable(
|
|
121
|
+
def __call__(self, **kwargs):
|
|
122
|
+
self._callable(**kwargs)
|
|
84
123
|
|
|
85
124
|
w = W(callable_)
|
|
86
125
|
|
|
@@ -129,10 +168,7 @@ class PayloadEndpoint:
|
|
|
129
168
|
return False
|
|
130
169
|
|
|
131
170
|
# Filter for the backend vortexes.
|
|
132
|
-
if
|
|
133
|
-
self._acceptOnlyFromVortex
|
|
134
|
-
and vortexName not in self._acceptOnlyFromVortex
|
|
135
|
-
):
|
|
171
|
+
if self._acceptOnlyFromVortex and vortexName not in self._acceptOnlyFromVortex:
|
|
136
172
|
return False
|
|
137
173
|
|
|
138
174
|
def removeUnhashable(filt):
|
|
@@ -162,33 +198,83 @@ class PayloadEndpoint:
|
|
|
162
198
|
|
|
163
199
|
return set(ourFilt).issubset(theirFilt)
|
|
164
200
|
|
|
201
|
+
@inlineCallbacks
|
|
165
202
|
def process(
|
|
166
203
|
self,
|
|
167
204
|
payloadEnvelope: PayloadEnvelope,
|
|
168
205
|
vortexUuid: str,
|
|
169
206
|
vortexName: str,
|
|
170
|
-
|
|
207
|
+
vortexSession,
|
|
171
208
|
sendResponse: SendVortexMsgResponseCallable,
|
|
172
209
|
):
|
|
210
|
+
from vortex.VortexFactory import VortexFactory
|
|
173
211
|
|
|
174
212
|
if not self.check(payloadEnvelope, vortexName):
|
|
175
213
|
return
|
|
176
214
|
|
|
177
|
-
if self.PERMITTER and not self.PERMITTER.check(
|
|
178
|
-
payloadEnvelope, httpSession
|
|
179
|
-
):
|
|
215
|
+
if self.PERMITTER and not self.PERMITTER.check(payloadEnvelope, vortexSession):
|
|
180
216
|
logger.debug("Permission failed for %s", payloadEnvelope.filt)
|
|
181
217
|
return
|
|
182
218
|
|
|
219
|
+
# Level 1: Endpoint permission check
|
|
220
|
+
auth = VortexFactory.getAuthenticator()
|
|
221
|
+
if self._requiredPermissions:
|
|
222
|
+
if not auth:
|
|
223
|
+
logger.error(
|
|
224
|
+
"Permission check for filt=%s cannot be enforced:"
|
|
225
|
+
" no authenticator is registered."
|
|
226
|
+
" Call VortexFactory.registerAuthenticator(). Request denied.",
|
|
227
|
+
payloadEnvelope.filt,
|
|
228
|
+
)
|
|
229
|
+
return
|
|
230
|
+
if not vortexSession:
|
|
231
|
+
logger.error(
|
|
232
|
+
"Permission check for filt=%s cannot be enforced:"
|
|
233
|
+
" no VortexSession found. Request denied.",
|
|
234
|
+
payloadEnvelope.filt,
|
|
235
|
+
)
|
|
236
|
+
return
|
|
237
|
+
endpointPermissions = yield maybeDeferred(
|
|
238
|
+
auth.getEndpointPermissions, vortexSession
|
|
239
|
+
)
|
|
240
|
+
if not self._requiredPermissions.issubset(endpointPermissions):
|
|
241
|
+
logger.warning(
|
|
242
|
+
"Permission denied for endpoint filt=%s",
|
|
243
|
+
payloadEnvelope.filt,
|
|
244
|
+
)
|
|
245
|
+
return
|
|
246
|
+
|
|
247
|
+
# Level 2: Get access control context, but only if the callback uses it.
|
|
248
|
+
# Default to the sentinel so handlers always receive a non-None value
|
|
249
|
+
# with a clear error on attribute access when auth is unavailable.
|
|
250
|
+
accessControlContext = NO_AUTH_ACCESS_CONTROL_CONTEXT
|
|
251
|
+
if self._callableAcceptsAccessControlContext and auth and vortexSession:
|
|
252
|
+
raw_ctx = yield maybeDeferred(
|
|
253
|
+
auth.getAccessControlContext, vortexSession, vortexName, vortexUuid
|
|
254
|
+
)
|
|
255
|
+
if not isinstance(raw_ctx, VortexAccessControlContextABC):
|
|
256
|
+
logger.error(
|
|
257
|
+
"Authenticator.getAccessControlContext() returned %r (type %s);"
|
|
258
|
+
" expected a VortexAccessControlContextABC subclass."
|
|
259
|
+
" accessControlContext will be the no-auth sentinel.",
|
|
260
|
+
raw_ctx,
|
|
261
|
+
type(raw_ctx).__name__,
|
|
262
|
+
)
|
|
263
|
+
else:
|
|
264
|
+
accessControlContext = raw_ctx
|
|
265
|
+
|
|
183
266
|
callable_ = self._wref()
|
|
184
267
|
if callable_:
|
|
185
|
-
|
|
268
|
+
kwargs = dict(
|
|
186
269
|
payloadEnvelope=payloadEnvelope,
|
|
187
270
|
vortexUuid=vortexUuid,
|
|
188
271
|
vortexName=vortexName,
|
|
189
|
-
httpSession=httpSession,
|
|
190
272
|
sendResponse=sendResponse,
|
|
273
|
+
vortexSession=vortexSession,
|
|
191
274
|
)
|
|
275
|
+
if self._callableAcceptsAccessControlContext:
|
|
276
|
+
kwargs["accessControlContext"] = accessControlContext
|
|
277
|
+
return callable_(**kwargs)
|
|
192
278
|
else:
|
|
193
279
|
PayloadIO().remove(self)
|
|
194
280
|
|