vortexpy 4.0.23__py3-none-any.whl
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.
- vortex/DataWrapTuple.py +27 -0
- vortex/DeferUtil.py +224 -0
- vortex/Jsonable.py +304 -0
- vortex/Payload.py +115 -0
- vortex/PayloadEndpoint.py +216 -0
- vortex/PayloadEndpointTest.py +164 -0
- vortex/PayloadEnvelope.py +166 -0
- vortex/PayloadFilterKeys.py +15 -0
- vortex/PayloadIO.py +152 -0
- vortex/PayloadPriority.py +11 -0
- vortex/PayloadResponse.py +173 -0
- vortex/PayloadResponseTest.py +39 -0
- vortex/PayloadTest.py +136 -0
- vortex/PerformTestActionTuple.py +30 -0
- vortex/SerialiseUtil.py +273 -0
- vortex/TestTuple.py +61 -0
- vortex/Tuple.py +968 -0
- vortex/TupleAction.py +72 -0
- vortex/TupleActionVortex.py +5 -0
- vortex/TupleDataForTest.py +25 -0
- vortex/TuplePerfTest.py +71 -0
- vortex/TuplePolymorphicTupleTypeFieldArgTest.py +74 -0
- vortex/TupleRestfulTest.py +140 -0
- vortex/TupleSelector.py +105 -0
- vortex/TupleTest.py +127 -0
- vortex/VortexABC.py +55 -0
- vortex/VortexClientHttp.py +308 -0
- vortex/VortexClientHttpTest.py +71 -0
- vortex/VortexClientTcp.py +355 -0
- vortex/VortexClientWebsocketFactory.py +383 -0
- vortex/VortexConnectionABC.py +88 -0
- vortex/VortexFactory.py +932 -0
- vortex/VortexPayloadProtocol.py +180 -0
- vortex/VortexServer.py +367 -0
- vortex/VortexServerConnection.py +127 -0
- vortex/VortexServerHttpResource.py +193 -0
- vortex/VortexServerTcp.py +104 -0
- vortex/VortexServerWebsocket.py +287 -0
- vortex/VortexTcpMemoryLeakPayloadTest.py +611 -0
- vortex/VortexTcpMemoryLeakRawTest.py +87 -0
- vortex/VortexTest.py +22 -0
- vortex/VortexUtil.py +314 -0
- vortex/VortexWritePushProducer.py +321 -0
- vortex/__init__.py +10 -0
- vortex/data_loader/TupleDataLiveLockManager.py +383 -0
- vortex/data_loader/TupleDataLoader.py +326 -0
- vortex/data_loader/TupleDataLoaderDelegate.py +90 -0
- vortex/data_loader/TupleDataLoaderTupleABC.py +10 -0
- vortex/data_loader/TupleDataLoaderTuples.py +53 -0
- vortex/data_loader/__init__.py +0 -0
- vortex/event_bus/TupleChangeEventABC.py +15 -0
- vortex/event_bus/TupleChangeEventBus.py +192 -0
- vortex/event_bus/TupleChangeEventBusObserverABC.py +11 -0
- vortex/event_bus/__init__.py +0 -0
- vortex/handler/TupleActionProcessor.py +270 -0
- vortex/handler/TupleActionProcessorProxy.py +218 -0
- vortex/handler/TupleActionProcessorTestHandler.py +27 -0
- vortex/handler/TupleDataActionClient.py +67 -0
- vortex/handler/TupleDataObservableCache.py +201 -0
- vortex/handler/TupleDataObservableHandler.py +371 -0
- vortex/handler/TupleDataObservableProxyHandler.py +340 -0
- vortex/handler/TupleDataObservableTestHandler.py +60 -0
- vortex/handler/TupleDataObserverClient.py +158 -0
- vortex/handler/VortexJSTupleLoaderTestHandler.py +42 -0
- vortex/handler/__init__.py +0 -0
- vortex/restful/GzippedDataHttpClient.py +170 -0
- vortex/restful/GzippedDataHttpClientTest.py +103 -0
- vortex/restful/GzippedPayloadHttpClient.py +181 -0
- vortex/restful/RestfulHttpClient.py +107 -0
- vortex/restful/RestfulHttpClientTest.py +60 -0
- vortex/restful/RestfulResource.py +153 -0
- vortex/restful/RestfulResourceTest.py +100 -0
- vortex/restful/__init__.py +0 -0
- vortex/rpc/RPC.py +446 -0
- vortex/rpc/RPCTest.py +12 -0
- vortex/rpc/__init__.py +0 -0
- vortex/sqla_orm/OrmCrudHandler.py +431 -0
- vortex/sqla_orm/TupleProviderForDb.py +42 -0
- vortex/sqla_orm/VortexTupleJson.py +24 -0
- vortex/sqla_orm/__init__.py +0 -0
- vortex/storage/TupleStorageSqlite.py +359 -0
- vortex/storage/__init__.py +0 -0
- vortexpy-4.0.23.dist-info/LICENSE +21 -0
- vortexpy-4.0.23.dist-info/METADATA +43 -0
- vortexpy-4.0.23.dist-info/RECORD +87 -0
- vortexpy-4.0.23.dist-info/WHEEL +5 -0
- vortexpy-4.0.23.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
* Created by Synerty Pty Ltd
|
|
3
|
+
*
|
|
4
|
+
* This software is open source, the MIT license applies.
|
|
5
|
+
*
|
|
6
|
+
* Website : https://www.synerty.com
|
|
7
|
+
* Support : support@synerty.com
|
|
8
|
+
"""
|
|
9
|
+
import abc
|
|
10
|
+
from abc import abstractmethod
|
|
11
|
+
from typing import Union, Optional
|
|
12
|
+
|
|
13
|
+
from twisted.internet.defer import Deferred
|
|
14
|
+
|
|
15
|
+
from vortex.TupleSelector import TupleSelector
|
|
16
|
+
from vortex.data_loader.TupleDataLoaderTupleABC import TupleDataLoaderTupleABC
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TupleDataLoaderDelegateABC(abc.ABC):
|
|
20
|
+
"""Tuple Data Loader Delegate ABC
|
|
21
|
+
|
|
22
|
+
ABC for delegate classes to handle the reading and writing for each forms'
|
|
23
|
+
data. Handlers must be register in
|
|
24
|
+
(`Tuple`, `TupleDataLoaderDelegateABC`) pairs with the `TupleDataLoader`
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def start(self):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
def shutdown(self):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def createData(
|
|
36
|
+
self, data: TupleDataLoaderTupleABC
|
|
37
|
+
) -> Union[Deferred, TupleSelector]:
|
|
38
|
+
"""Load Data
|
|
39
|
+
|
|
40
|
+
The `createData` method is called when data does not exist and needs
|
|
41
|
+
to be created.
|
|
42
|
+
|
|
43
|
+
:param data: The new object to create
|
|
44
|
+
:param selector: The current tuple selector for the data.
|
|
45
|
+
:return: The `TupleSelector` or a `Deferred` which eventually
|
|
46
|
+
results in a `TupleSelector`. The tuple selector must select the new
|
|
47
|
+
data.
|
|
48
|
+
"""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def loadData(
|
|
53
|
+
self, selector: TupleSelector
|
|
54
|
+
) -> Union[Deferred, TupleDataLoaderTupleABC]:
|
|
55
|
+
"""Load Data
|
|
56
|
+
|
|
57
|
+
The `loadData` method is called when data needs to be loaded from a
|
|
58
|
+
storage medium. The query is represented by a `TupleSelector` value
|
|
59
|
+
`selector`.
|
|
60
|
+
|
|
61
|
+
:param selector: The `TupleSelector` for which to load data
|
|
62
|
+
:return: The `Tuple` or a `Deferred` which eventually results in a `Tuple`
|
|
63
|
+
"""
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def storeData(
|
|
68
|
+
self, data: TupleDataLoaderTupleABC, tupleSelector: TupleSelector
|
|
69
|
+
) -> Optional[TupleSelector]:
|
|
70
|
+
"""Store Data
|
|
71
|
+
|
|
72
|
+
Store the data into the storage medium. The `data` is the `Tuple` which
|
|
73
|
+
needs to be written
|
|
74
|
+
|
|
75
|
+
:param data: Tuple to write and store
|
|
76
|
+
:param selector: Previous selector used to get the data
|
|
77
|
+
:return: TupleSelector: An optional new tuple selector if it has changed
|
|
78
|
+
"""
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
@abstractmethod
|
|
82
|
+
def deleteData(self, selector: TupleSelector) -> Deferred:
|
|
83
|
+
"""Delete Data
|
|
84
|
+
|
|
85
|
+
Delete the data referenced by the `selector`
|
|
86
|
+
|
|
87
|
+
:param selector: `TupleSelector` for data to be deleted
|
|
88
|
+
:return: Deferred
|
|
89
|
+
"""
|
|
90
|
+
pass
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from vortex.Tuple import addTupleType, TupleField, Tuple
|
|
4
|
+
from vortex.TupleAction import TupleActionABC
|
|
5
|
+
from vortex.TupleSelector import TupleSelector
|
|
6
|
+
from vortex.data_loader.TupleDataLoaderTupleABC import TupleDataLoaderTupleABC
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@addTupleType
|
|
10
|
+
class _DataLoaderTupleAction(TupleActionABC):
|
|
11
|
+
__tupleType__ = "vortex._DataLoaderTupleAction"
|
|
12
|
+
|
|
13
|
+
CREATE_ACTION = "create"
|
|
14
|
+
STORE_ACTION = "store"
|
|
15
|
+
LOAD_ACTION = "load"
|
|
16
|
+
DELETE_ACTION = "delete"
|
|
17
|
+
|
|
18
|
+
userUuid: str = TupleField()
|
|
19
|
+
delegateUuid: str = TupleField()
|
|
20
|
+
dataTuple: TupleDataLoaderTupleABC = TupleField()
|
|
21
|
+
action: str = TupleField()
|
|
22
|
+
tupleDataSelector: TupleSelector = TupleField()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@addTupleType
|
|
26
|
+
class _DataLoaderTupleActionResponseTuple(Tuple):
|
|
27
|
+
__tupleType__ = "vortex._DataLoaderTupleActionResponseTuple"
|
|
28
|
+
|
|
29
|
+
tupleData: Optional[TupleDataLoaderTupleABC] = TupleField()
|
|
30
|
+
tupleDataSelector: Optional[TupleSelector] = TupleField()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@addTupleType
|
|
34
|
+
class _LockDataTupleAction(TupleActionABC):
|
|
35
|
+
__tupleType__ = "vortex._LockDataTupleAction"
|
|
36
|
+
|
|
37
|
+
lock: bool = TupleField()
|
|
38
|
+
userUuid: str = TupleField()
|
|
39
|
+
delegateUuid: str = TupleField()
|
|
40
|
+
tupleDataSelector: TupleSelector = TupleField()
|
|
41
|
+
liveUpdateDataTuple: TupleDataLoaderTupleABC = TupleField()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@addTupleType
|
|
45
|
+
class _DataLockStatusTuple(Tuple):
|
|
46
|
+
__tupleType__ = "vortex._DataLockStatusTuple"
|
|
47
|
+
|
|
48
|
+
locked: bool = TupleField(False)
|
|
49
|
+
deleted: bool = TupleField(False)
|
|
50
|
+
lockedByUserUuid: Optional[str] = TupleField()
|
|
51
|
+
lockAutoExpireDate: Optional[str] = TupleField()
|
|
52
|
+
liveUpdateDataFromDelegateUuid: Optional[str] = TupleField()
|
|
53
|
+
liveUpdateDataTuple: Optional[TupleDataLoaderTupleABC] = TupleField()
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from abc import ABCMeta
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TupleChangeEventABC(metaclass=ABCMeta):
|
|
6
|
+
__eventType__ = None
|
|
7
|
+
|
|
8
|
+
@classmethod
|
|
9
|
+
def eventType(cls) -> str:
|
|
10
|
+
return cls.__eventType__
|
|
11
|
+
|
|
12
|
+
def __repr__(self):
|
|
13
|
+
return json.dumps(
|
|
14
|
+
{"class": self.__class__.__name__, "fields": self.__dict__}
|
|
15
|
+
)
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from _weakrefset import WeakSet
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Optional, Type, Union
|
|
6
|
+
|
|
7
|
+
import pytz
|
|
8
|
+
from twisted.internet import reactor
|
|
9
|
+
from twisted.internet.defer import inlineCallbacks
|
|
10
|
+
|
|
11
|
+
from vortex.TupleSelector import TupleSelector
|
|
12
|
+
from vortex.event_bus.TupleChangeEventABC import TupleChangeEventABC
|
|
13
|
+
from vortex.event_bus.TupleChangeEventBusObserverABC import (
|
|
14
|
+
TupleChangeEventBusObserverABC,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TupleChangeEventBus:
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self._weakObserversForAllTupleChangeEventABCNames: WeakSet[
|
|
23
|
+
TupleChangeEventBusObserverABC
|
|
24
|
+
] = WeakSet([])
|
|
25
|
+
|
|
26
|
+
self._weakObserversBySelectorName: dict[
|
|
27
|
+
str, WeakSet[TupleChangeEventBusObserverABC]
|
|
28
|
+
] = defaultdict(lambda: WeakSet([]))
|
|
29
|
+
|
|
30
|
+
def notifyMany(self, events: list[TupleChangeEventABC]) -> None:
|
|
31
|
+
for event in events:
|
|
32
|
+
self.notify(event)
|
|
33
|
+
|
|
34
|
+
def notify(self, event: TupleChangeEventABC) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Notify
|
|
37
|
+
|
|
38
|
+
Notify the bus of a change to a tuple.
|
|
39
|
+
|
|
40
|
+
This bus does not automatically call
|
|
41
|
+
TupleDataObservableHandler.notifyOfTupleUpdate(...)
|
|
42
|
+
that must be done by classes implementing
|
|
43
|
+
TupleChangeEventBusObserverABC
|
|
44
|
+
|
|
45
|
+
IMPORTANT: You can use this bus for whatever you like,
|
|
46
|
+
however, see best practices.
|
|
47
|
+
|
|
48
|
+
BEST PRACTICE: Only notify the bus of precisely
|
|
49
|
+
the data that has changed, as close to the code that changes it as
|
|
50
|
+
possible.
|
|
51
|
+
|
|
52
|
+
Do not notify the bus of tuple selectors that need updating.
|
|
53
|
+
|
|
54
|
+
Classes implementing TupleChangeEventBusObserverABC
|
|
55
|
+
should observe changed data and themselves
|
|
56
|
+
decide what tuple selectors they will
|
|
57
|
+
notify their observers for.
|
|
58
|
+
|
|
59
|
+
For example. If you have two tuple selectors:
|
|
60
|
+
|
|
61
|
+
1) TupleChangeEventABC(MyTuple, {id:1})
|
|
62
|
+
2) TupleChangeEventABC(ListOfMyTuples)
|
|
63
|
+
|
|
64
|
+
TupleChangeEventBus.notify should be called for MyTuple
|
|
65
|
+
just after changes are committed.
|
|
66
|
+
|
|
67
|
+
TupleChangeEventBus.notify SHOULD NOT BE called for
|
|
68
|
+
ListOfMyTuples
|
|
69
|
+
|
|
70
|
+
A typical observer's method might look like this:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
def notifyFromBus(self, event: TupleChangeEventABC) -> None:
|
|
74
|
+
if event.isForTuple(MyTuple):
|
|
75
|
+
self.tupleDataObservable.notifyOfTupleUpdate(event)
|
|
76
|
+
self.tupleDataObservable.notifyOfTupleUpdate(
|
|
77
|
+
TupleChangeEventABC(ListOfMyTuples)
|
|
78
|
+
)
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
...
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
:param event: The tuple selector that describes the tuple
|
|
86
|
+
that has been changed. This change might be created, updated, or
|
|
87
|
+
deleted.
|
|
88
|
+
:return:
|
|
89
|
+
"""
|
|
90
|
+
weakSets = [self._weakObserversForAllTupleChangeEventABCNames]
|
|
91
|
+
|
|
92
|
+
eventName = event.eventType()
|
|
93
|
+
|
|
94
|
+
if eventName in self._weakObserversBySelectorName:
|
|
95
|
+
weakSets.append(list(self._weakObserversBySelectorName[eventName]))
|
|
96
|
+
|
|
97
|
+
for weakSet in weakSets:
|
|
98
|
+
for item in weakSet:
|
|
99
|
+
if not item:
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
# noinspection PyUnresolvedReferences
|
|
103
|
+
reactor.callLater(0, self._notify, item, event)
|
|
104
|
+
|
|
105
|
+
@inlineCallbacks
|
|
106
|
+
def _notify(
|
|
107
|
+
self,
|
|
108
|
+
observable: TupleChangeEventBusObserverABC,
|
|
109
|
+
event: TupleChangeEventABC,
|
|
110
|
+
):
|
|
111
|
+
try:
|
|
112
|
+
startDate = datetime.now(pytz.utc)
|
|
113
|
+
yield observable.notifyFromBus(event)
|
|
114
|
+
|
|
115
|
+
# A blocking call taking more than 100ms is BAD
|
|
116
|
+
# Otherwise a call taking more than a 1s is just poor performance.
|
|
117
|
+
secondsTaken = (datetime.now(pytz.utc) - startDate).total_seconds()
|
|
118
|
+
if 0.1 < secondsTaken:
|
|
119
|
+
logger.debug(
|
|
120
|
+
"notifyFromBus took %s for %s, event %s",
|
|
121
|
+
secondsTaken,
|
|
122
|
+
observable.__class__.__name__,
|
|
123
|
+
event,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
logger.error(
|
|
128
|
+
"Observable %s raised an error"
|
|
129
|
+
" when being called with event %s",
|
|
130
|
+
observable.__class__.__name__,
|
|
131
|
+
event,
|
|
132
|
+
)
|
|
133
|
+
logger.exception(e)
|
|
134
|
+
|
|
135
|
+
def addObserver(
|
|
136
|
+
self,
|
|
137
|
+
observer: TupleChangeEventBusObserverABC,
|
|
138
|
+
eventNameOrClass: Optional[
|
|
139
|
+
Union[
|
|
140
|
+
str,
|
|
141
|
+
Type[TupleChangeEventABC],
|
|
142
|
+
TupleChangeEventABC,
|
|
143
|
+
]
|
|
144
|
+
] = None,
|
|
145
|
+
) -> None:
|
|
146
|
+
assert isinstance(
|
|
147
|
+
observer, TupleChangeEventBusObserverABC
|
|
148
|
+
), "observer must be a TupleChangeEventBusObserverABC"
|
|
149
|
+
|
|
150
|
+
if eventNameOrClass is None:
|
|
151
|
+
self._weakObserversForAllTupleChangeEventABCNames.add(observer)
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
eventName = self._nameFromEvent(eventNameOrClass)
|
|
155
|
+
|
|
156
|
+
self._weakObserversBySelectorName[eventName].add(observer)
|
|
157
|
+
|
|
158
|
+
# noinspection PyMethodMayBeStatic
|
|
159
|
+
def _nameFromEvent(
|
|
160
|
+
self,
|
|
161
|
+
nameOrTuple: Optional[
|
|
162
|
+
Union[str, Type[TupleChangeEventABC], TupleChangeEventABC]
|
|
163
|
+
],
|
|
164
|
+
) -> Optional[str]:
|
|
165
|
+
# When the TupleSelector is reconstructed / deserialize,
|
|
166
|
+
# it's constructed with
|
|
167
|
+
# no arguments
|
|
168
|
+
if nameOrTuple is None:
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
if isinstance(nameOrTuple, str):
|
|
172
|
+
name = nameOrTuple
|
|
173
|
+
|
|
174
|
+
elif isinstance(nameOrTuple, TupleChangeEventABC):
|
|
175
|
+
name = nameOrTuple.eventType()
|
|
176
|
+
|
|
177
|
+
elif issubclass(nameOrTuple, TupleChangeEventABC):
|
|
178
|
+
name = nameOrTuple.eventType()
|
|
179
|
+
|
|
180
|
+
else:
|
|
181
|
+
raise Exception(
|
|
182
|
+
"Argument should be of type "
|
|
183
|
+
"Union[str, Type[TupleChangeEventABC], TupleChangeEventABC]"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if name == TupleSelector.tupleName():
|
|
187
|
+
raise Exception(
|
|
188
|
+
"We've found the name of TupleSelector.tupleType(),"
|
|
189
|
+
"which means we've been passed a TupleSelector."
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
return name
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from abc import ABCMeta, abstractmethod
|
|
2
|
+
|
|
3
|
+
from vortex.event_bus.TupleChangeEventABC import TupleChangeEventABC
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TupleChangeEventBusObserverABC(metaclass=ABCMeta):
|
|
7
|
+
@abstractmethod
|
|
8
|
+
def notifyFromBus(self, event: TupleChangeEventABC) -> None:
|
|
9
|
+
raise NotImplementedError(
|
|
10
|
+
"TupleChangeEventBusObserverABC.notifyFromBus"
|
|
11
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from abc import ABCMeta
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from inspect import signature
|
|
5
|
+
from typing import Dict
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from typing import Union
|
|
8
|
+
|
|
9
|
+
from twisted.internet.defer import Deferred
|
|
10
|
+
from twisted.internet.defer import fail
|
|
11
|
+
from twisted.internet.defer import inlineCallbacks
|
|
12
|
+
from twisted.internet.defer import succeed
|
|
13
|
+
from twisted.python import failure
|
|
14
|
+
from twisted.python.failure import Failure
|
|
15
|
+
|
|
16
|
+
from vortex.DeferUtil import vortexLogFailure
|
|
17
|
+
from vortex.Payload import Payload
|
|
18
|
+
from vortex.PayloadEndpoint import PayloadEndpoint
|
|
19
|
+
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
20
|
+
from vortex.TupleAction import TupleActionABC
|
|
21
|
+
from vortex.TupleActionVortex import TupleActionVortex
|
|
22
|
+
from vortex.VortexABC import SendVortexMsgResponseCallable
|
|
23
|
+
from vortex.VortexFactory import VortexFactory
|
|
24
|
+
from vortex.VortexUtil import limitConcurrency
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TupleActionProcessorDelegateABC(metaclass=ABCMeta):
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def processTupleAction(
|
|
32
|
+
self, tupleAction: TupleActionABC, **kwargs
|
|
33
|
+
) -> Deferred:
|
|
34
|
+
"""Process Tuple Action
|
|
35
|
+
|
|
36
|
+
The method generates the vortexMsg for the vortex to send.
|
|
37
|
+
|
|
38
|
+
:param tupleAction: The C{TupleAction} to process.
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TupleActionProcessor:
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
tupleActionProcessorName: str,
|
|
47
|
+
additionalFilt: Optional[Dict] = None,
|
|
48
|
+
defaultDelegate: Optional[TupleActionProcessorDelegateABC] = None,
|
|
49
|
+
acceptOnlyFromVortex: Optional[Union[str, tuple]] = None,
|
|
50
|
+
usedForProxy__=False,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Constructor
|
|
53
|
+
|
|
54
|
+
:param tupleActionProcessorName: The name of this observable
|
|
55
|
+
|
|
56
|
+
:param additionalFilt: Any additional filter keys that are required
|
|
57
|
+
|
|
58
|
+
:param defaultDelegate: The default delegate to send all actions to
|
|
59
|
+
|
|
60
|
+
:param acceptOnlyFromVortex: Accept requests only from this vortex,
|
|
61
|
+
The vortex can be str or tuple of str, or None to accept from any.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
self._tupleActionProcessorName = tupleActionProcessorName
|
|
65
|
+
self._defaultDelegate = defaultDelegate
|
|
66
|
+
|
|
67
|
+
self._tupleProcessorsByTupleName: Dict[
|
|
68
|
+
str, TupleActionProcessorDelegateABC
|
|
69
|
+
] = {}
|
|
70
|
+
|
|
71
|
+
if not usedForProxy__:
|
|
72
|
+
self._filt = dict(
|
|
73
|
+
name=tupleActionProcessorName, key="tupleActionProcessorName"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if additionalFilt:
|
|
77
|
+
self._filt.update(additionalFilt)
|
|
78
|
+
|
|
79
|
+
self._endpoint = PayloadEndpoint(
|
|
80
|
+
self._filt,
|
|
81
|
+
self._processLimited,
|
|
82
|
+
acceptOnlyFromVortex=acceptOnlyFromVortex,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def setDelegate(
|
|
86
|
+
self, tupleName: str, processor: TupleActionProcessorDelegateABC
|
|
87
|
+
):
|
|
88
|
+
"""Add Tuple Action Processor Delegate
|
|
89
|
+
|
|
90
|
+
:param tupleName: The tuple name to process actions for.
|
|
91
|
+
:param processor: The processor to use for processing this tuple name.
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
assert (
|
|
95
|
+
not tupleName in self._tupleProcessorsByTupleName
|
|
96
|
+
), "TupleActionProcessor:%s, Tuple name %s is already registered" % (
|
|
97
|
+
self._tupleActionProcessorName,
|
|
98
|
+
tupleName,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
assert isinstance(processor, TupleActionProcessorDelegateABC), (
|
|
102
|
+
"TupleActionProcessor:%s, provider must be an"
|
|
103
|
+
" instance of TupleActionProcessorDelegateABC"
|
|
104
|
+
% self._tupleActionProcessorName
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
self._tupleProcessorsByTupleName[tupleName] = processor
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def delegateCount(self) -> int:
|
|
111
|
+
return len(self._tupleProcessorsByTupleName)
|
|
112
|
+
|
|
113
|
+
def hasDelegate(self, tupleName: str) -> bool:
|
|
114
|
+
return tupleName in self._tupleProcessorsByTupleName
|
|
115
|
+
|
|
116
|
+
def shutdown(self):
|
|
117
|
+
self._endpoint.shutdown()
|
|
118
|
+
|
|
119
|
+
def _processLimited(self, *args, **kwargs):
|
|
120
|
+
d = self._processLimitedDecorated(*args, **kwargs)
|
|
121
|
+
if d:
|
|
122
|
+
d.addErrback(vortexLogFailure, logger, consumeError=True)
|
|
123
|
+
|
|
124
|
+
@limitConcurrency(logger, 25)
|
|
125
|
+
def _processLimitedDecorated(self, *args, **kwargs):
|
|
126
|
+
return self._process(*args, **kwargs)
|
|
127
|
+
|
|
128
|
+
@inlineCallbacks
|
|
129
|
+
def _process(
|
|
130
|
+
self,
|
|
131
|
+
payloadEnvelope: PayloadEnvelope,
|
|
132
|
+
vortexUuid: str,
|
|
133
|
+
sendResponse: SendVortexMsgResponseCallable,
|
|
134
|
+
**kwargs,
|
|
135
|
+
):
|
|
136
|
+
"""Process the Payload / Tuple Action"""
|
|
137
|
+
if not VortexFactory.isVortexUuidOnline(vortexUuid):
|
|
138
|
+
logger.debug(
|
|
139
|
+
"Vortex %s is no longer online, skipping reply", vortexUuid
|
|
140
|
+
)
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
# logger.debug(
|
|
144
|
+
# "Vortex %s is STILL online, continuing to reply", vortexUuid
|
|
145
|
+
# )
|
|
146
|
+
|
|
147
|
+
payload = yield payloadEnvelope.decodePayloadDefer()
|
|
148
|
+
|
|
149
|
+
assert (
|
|
150
|
+
len(payload.tuples) == 1
|
|
151
|
+
), "TupleActionProcessor:%s Expected 1 tuples, received %s" % (
|
|
152
|
+
self._tupleActionProcessorName,
|
|
153
|
+
len(payload.tuples),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
tupleAction = payload.tuples[0]
|
|
157
|
+
|
|
158
|
+
yield self._processTupleAction(
|
|
159
|
+
payloadEnvelope.filt,
|
|
160
|
+
sendResponse,
|
|
161
|
+
tupleAction,
|
|
162
|
+
vortexUuid=vortexUuid,
|
|
163
|
+
**kwargs,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def _processTupleAction(
|
|
167
|
+
self, payloadEnvelopeFilt, sendResponse, tupleAction, **kwargs
|
|
168
|
+
):
|
|
169
|
+
|
|
170
|
+
assert isinstance(
|
|
171
|
+
tupleAction, TupleActionABC
|
|
172
|
+
), "TupleActionProcessor:%s Expected TupleAction, received %s" % (
|
|
173
|
+
self._tupleActionProcessorName,
|
|
174
|
+
tupleAction.__class__,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
tupleName = tupleAction.tupleName()
|
|
178
|
+
processor = self._tupleProcessorsByTupleName.get(tupleName)
|
|
179
|
+
if processor:
|
|
180
|
+
delegate = processor.processTupleAction
|
|
181
|
+
|
|
182
|
+
elif self._defaultDelegate:
|
|
183
|
+
delegate = self._defaultDelegate.processTupleAction
|
|
184
|
+
|
|
185
|
+
else:
|
|
186
|
+
raise Exception("No delegate registered for %s" % tupleName)
|
|
187
|
+
|
|
188
|
+
sig = signature(delegate)
|
|
189
|
+
if len(sig.parameters) == 2:
|
|
190
|
+
tupleActionVortex = TupleActionVortex(**kwargs)
|
|
191
|
+
d = self._customMaybeDeferred(
|
|
192
|
+
delegate, tupleAction, tupleActionVortex
|
|
193
|
+
)
|
|
194
|
+
else:
|
|
195
|
+
d = self._customMaybeDeferred(delegate, tupleAction)
|
|
196
|
+
|
|
197
|
+
d.addCallback(
|
|
198
|
+
self._callback, payloadEnvelopeFilt, tupleName, sendResponse
|
|
199
|
+
)
|
|
200
|
+
d.addErrback(
|
|
201
|
+
self._errback, payloadEnvelopeFilt, tupleName, sendResponse
|
|
202
|
+
)
|
|
203
|
+
return d
|
|
204
|
+
|
|
205
|
+
@inlineCallbacks
|
|
206
|
+
def _callback(
|
|
207
|
+
self,
|
|
208
|
+
result,
|
|
209
|
+
replyFilt: dict,
|
|
210
|
+
tupleName: str,
|
|
211
|
+
sendResponse: SendVortexMsgResponseCallable,
|
|
212
|
+
):
|
|
213
|
+
|
|
214
|
+
if not isinstance(result, list):
|
|
215
|
+
result = [result]
|
|
216
|
+
payload = Payload(filt=replyFilt, tuples=result)
|
|
217
|
+
|
|
218
|
+
payloadEnvelope = yield payload.makePayloadEnvelopeDefer()
|
|
219
|
+
vortexMsg = yield payloadEnvelope.toVortexMsgDefer()
|
|
220
|
+
try:
|
|
221
|
+
yield sendResponse(vortexMsg)
|
|
222
|
+
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logger.error(
|
|
225
|
+
"Failed to send TupleAction response for %s", tupleName
|
|
226
|
+
)
|
|
227
|
+
logger.exception(e)
|
|
228
|
+
|
|
229
|
+
@inlineCallbacks
|
|
230
|
+
def _errback(
|
|
231
|
+
self,
|
|
232
|
+
result: Failure,
|
|
233
|
+
replyFilt: dict,
|
|
234
|
+
tupleName: str,
|
|
235
|
+
sendResponse: SendVortexMsgResponseCallable,
|
|
236
|
+
):
|
|
237
|
+
|
|
238
|
+
logger.error(
|
|
239
|
+
"TupleActionProcessor:%s Failed to process TupleActon",
|
|
240
|
+
self._tupleActionProcessorName,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
vortexLogFailure(result, logger)
|
|
244
|
+
failureMessage = result.getErrorMessage()
|
|
245
|
+
|
|
246
|
+
payloadEnvelope = PayloadEnvelope(filt=replyFilt, result=failureMessage)
|
|
247
|
+
vortexMsg = yield payloadEnvelope.toVortexMsgDefer()
|
|
248
|
+
try:
|
|
249
|
+
yield sendResponse(vortexMsg)
|
|
250
|
+
|
|
251
|
+
except Exception as e:
|
|
252
|
+
logger.error(
|
|
253
|
+
"Failed to send TupleAction response for %s\n%s",
|
|
254
|
+
tupleName,
|
|
255
|
+
failureMessage,
|
|
256
|
+
)
|
|
257
|
+
logger.exception(e)
|
|
258
|
+
|
|
259
|
+
def _customMaybeDeferred(self, f, *args, **kw):
|
|
260
|
+
try:
|
|
261
|
+
result = f(*args, **kw)
|
|
262
|
+
except Exception as e:
|
|
263
|
+
return fail(failure.Failure(e))
|
|
264
|
+
|
|
265
|
+
if isinstance(result, Deferred):
|
|
266
|
+
return result
|
|
267
|
+
elif isinstance(result, failure.Failure):
|
|
268
|
+
return fail(result)
|
|
269
|
+
else:
|
|
270
|
+
return succeed(result)
|