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.
Files changed (87) hide show
  1. vortex/DataWrapTuple.py +27 -0
  2. vortex/DeferUtil.py +224 -0
  3. vortex/Jsonable.py +304 -0
  4. vortex/Payload.py +115 -0
  5. vortex/PayloadEndpoint.py +216 -0
  6. vortex/PayloadEndpointTest.py +164 -0
  7. vortex/PayloadEnvelope.py +166 -0
  8. vortex/PayloadFilterKeys.py +15 -0
  9. vortex/PayloadIO.py +152 -0
  10. vortex/PayloadPriority.py +11 -0
  11. vortex/PayloadResponse.py +173 -0
  12. vortex/PayloadResponseTest.py +39 -0
  13. vortex/PayloadTest.py +136 -0
  14. vortex/PerformTestActionTuple.py +30 -0
  15. vortex/SerialiseUtil.py +273 -0
  16. vortex/TestTuple.py +61 -0
  17. vortex/Tuple.py +968 -0
  18. vortex/TupleAction.py +72 -0
  19. vortex/TupleActionVortex.py +5 -0
  20. vortex/TupleDataForTest.py +25 -0
  21. vortex/TuplePerfTest.py +71 -0
  22. vortex/TuplePolymorphicTupleTypeFieldArgTest.py +74 -0
  23. vortex/TupleRestfulTest.py +140 -0
  24. vortex/TupleSelector.py +105 -0
  25. vortex/TupleTest.py +127 -0
  26. vortex/VortexABC.py +55 -0
  27. vortex/VortexClientHttp.py +308 -0
  28. vortex/VortexClientHttpTest.py +71 -0
  29. vortex/VortexClientTcp.py +355 -0
  30. vortex/VortexClientWebsocketFactory.py +383 -0
  31. vortex/VortexConnectionABC.py +88 -0
  32. vortex/VortexFactory.py +932 -0
  33. vortex/VortexPayloadProtocol.py +180 -0
  34. vortex/VortexServer.py +367 -0
  35. vortex/VortexServerConnection.py +127 -0
  36. vortex/VortexServerHttpResource.py +193 -0
  37. vortex/VortexServerTcp.py +104 -0
  38. vortex/VortexServerWebsocket.py +287 -0
  39. vortex/VortexTcpMemoryLeakPayloadTest.py +611 -0
  40. vortex/VortexTcpMemoryLeakRawTest.py +87 -0
  41. vortex/VortexTest.py +22 -0
  42. vortex/VortexUtil.py +314 -0
  43. vortex/VortexWritePushProducer.py +321 -0
  44. vortex/__init__.py +10 -0
  45. vortex/data_loader/TupleDataLiveLockManager.py +383 -0
  46. vortex/data_loader/TupleDataLoader.py +326 -0
  47. vortex/data_loader/TupleDataLoaderDelegate.py +90 -0
  48. vortex/data_loader/TupleDataLoaderTupleABC.py +10 -0
  49. vortex/data_loader/TupleDataLoaderTuples.py +53 -0
  50. vortex/data_loader/__init__.py +0 -0
  51. vortex/event_bus/TupleChangeEventABC.py +15 -0
  52. vortex/event_bus/TupleChangeEventBus.py +192 -0
  53. vortex/event_bus/TupleChangeEventBusObserverABC.py +11 -0
  54. vortex/event_bus/__init__.py +0 -0
  55. vortex/handler/TupleActionProcessor.py +270 -0
  56. vortex/handler/TupleActionProcessorProxy.py +218 -0
  57. vortex/handler/TupleActionProcessorTestHandler.py +27 -0
  58. vortex/handler/TupleDataActionClient.py +67 -0
  59. vortex/handler/TupleDataObservableCache.py +201 -0
  60. vortex/handler/TupleDataObservableHandler.py +371 -0
  61. vortex/handler/TupleDataObservableProxyHandler.py +340 -0
  62. vortex/handler/TupleDataObservableTestHandler.py +60 -0
  63. vortex/handler/TupleDataObserverClient.py +158 -0
  64. vortex/handler/VortexJSTupleLoaderTestHandler.py +42 -0
  65. vortex/handler/__init__.py +0 -0
  66. vortex/restful/GzippedDataHttpClient.py +170 -0
  67. vortex/restful/GzippedDataHttpClientTest.py +103 -0
  68. vortex/restful/GzippedPayloadHttpClient.py +181 -0
  69. vortex/restful/RestfulHttpClient.py +107 -0
  70. vortex/restful/RestfulHttpClientTest.py +60 -0
  71. vortex/restful/RestfulResource.py +153 -0
  72. vortex/restful/RestfulResourceTest.py +100 -0
  73. vortex/restful/__init__.py +0 -0
  74. vortex/rpc/RPC.py +446 -0
  75. vortex/rpc/RPCTest.py +12 -0
  76. vortex/rpc/__init__.py +0 -0
  77. vortex/sqla_orm/OrmCrudHandler.py +431 -0
  78. vortex/sqla_orm/TupleProviderForDb.py +42 -0
  79. vortex/sqla_orm/VortexTupleJson.py +24 -0
  80. vortex/sqla_orm/__init__.py +0 -0
  81. vortex/storage/TupleStorageSqlite.py +359 -0
  82. vortex/storage/__init__.py +0 -0
  83. vortexpy-4.0.23.dist-info/LICENSE +21 -0
  84. vortexpy-4.0.23.dist-info/METADATA +43 -0
  85. vortexpy-4.0.23.dist-info/RECORD +87 -0
  86. vortexpy-4.0.23.dist-info/WHEEL +5 -0
  87. vortexpy-4.0.23.dist-info/top_level.txt +1 -0
@@ -0,0 +1,181 @@
1
+ import gzip
2
+ import logging
3
+ import struct
4
+ from datetime import datetime
5
+ from io import BytesIO
6
+ from typing import Union, List, Type
7
+
8
+ import pytz
9
+ from twisted.internet import reactor
10
+ from twisted.internet.defer import Deferred, inlineCallbacks
11
+ from twisted.internet.defer import succeed
12
+ from twisted.internet.protocol import Protocol
13
+ from twisted.web._newclient import ResponseDone
14
+ from twisted.web.client import Agent, GzipDecoder, ContentDecoderAgent
15
+ from twisted.web.http_headers import Headers
16
+ from twisted.web.iweb import IBodyProducer, UNKNOWN_LENGTH
17
+ from zope.interface import implementer
18
+
19
+ from vortex.Tuple import Tuple
20
+ from vortex.restful.RestfulResource import HTTP_REQUEST
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class GzippedPayloadHttpClient:
26
+ """A Restful http client that sends out a binary payload"""
27
+
28
+ DEFAULT_HEADERS = {
29
+ "User-Agent": ["synerty/1.0"],
30
+ "Content-Type": ["application/json"],
31
+ }
32
+
33
+ def __init__(
34
+ self,
35
+ url: Union[str, bytes],
36
+ payload: bytes,
37
+ headers: dict[str, List[str]],
38
+ method: HTTP_REQUEST,
39
+ meta: Type[Tuple],
40
+ isPayloadGzipped: bool = True,
41
+ compressed: bool = True,
42
+ timeout: float = 10,
43
+ ):
44
+ self._httpMethod = method.value.encode()
45
+ self._url = url.encode() if isinstance(url, str) else url
46
+ self._payload = payload
47
+ self._headers = {**self.DEFAULT_HEADERS, **headers}
48
+ self._meta = meta
49
+ self._compressed = (
50
+ compressed # send http requests whether gzip-compressed or not
51
+ )
52
+ self._isPayloadGzipped = isPayloadGzipped
53
+ self._timeout = timeout # connect timeout
54
+
55
+ @inlineCallbacks
56
+ def run(self) -> Type[Tuple]:
57
+ agent = Agent(reactor, connectTimeout=self._timeout)
58
+ # Add the gzip decoder
59
+ if self._compressed:
60
+ agent = ContentDecoderAgent(agent, [(b"gzip", GzipDecoder)])
61
+
62
+ binaryPayloadRequestProducer = _BinaryPayloadRequestProducer(
63
+ self._payload,
64
+ self._meta,
65
+ self._isPayloadGzipped,
66
+ )
67
+ # Make the web request
68
+ response = yield agent.request(
69
+ self._httpMethod,
70
+ self._url,
71
+ Headers(self._headers),
72
+ binaryPayloadRequestProducer,
73
+ )
74
+
75
+ self._meta = binaryPayloadRequestProducer.meta
76
+ self._meta.code = response.code
77
+ self._meta.version = response.version
78
+ self._meta.headers = {
79
+ k.decode(): v[0].decode()
80
+ for k, v in response.headers.getAllRawHeaders()
81
+ }
82
+
83
+ # Get the responseTuple data
84
+ responseProducer = self._cbResponse(response, self._meta)
85
+
86
+ self._meta = responseProducer.meta
87
+ return self._meta
88
+ # return GzippedPayloadHttpResponse(
89
+ # code=response.code,
90
+ # version=response.version,
91
+ # headers={
92
+ # k.decode(): v[0].decode()
93
+ # for k, v in response.headers.getAllRawHeaders()
94
+ # },
95
+ # body=responseProducer.asyncData,
96
+ # requestDate=meta.requestDate,
97
+ # responseDate=meta.responseDate,
98
+ # )
99
+
100
+ def _cbResponse(self, response, meta):
101
+ responseProducer = _ResponseProducer(meta)
102
+ response.deliverBody(responseProducer)
103
+ return responseProducer
104
+
105
+
106
+ @implementer(IBodyProducer)
107
+ class _BinaryPayloadRequestProducer:
108
+ def __init__(
109
+ self,
110
+ payload: bytes,
111
+ meta: Type[Tuple],
112
+ isPayloadGzipped: bool = True,
113
+ ):
114
+ self._meta = meta
115
+ self._payload = payload
116
+ self._isGzippedAlready = isPayloadGzipped
117
+ self.length = self._getGzipUncompressedSize()
118
+ self._gzipFile = None
119
+
120
+ @property
121
+ def meta(self):
122
+ return self._meta
123
+
124
+ def startProducing(self, consumer):
125
+ self._meta.requestDate = datetime.now(tz=pytz.utc)
126
+
127
+ if self._isGzippedAlready:
128
+ try:
129
+ decompressed = gzip.decompress(self._payload)
130
+ logger.info(f"declared payload length: {self.length}")
131
+ logger.info(f"decompressed payload length: {len(decompressed)}")
132
+ consumer.write(decompressed)
133
+ except Exception as e:
134
+ raise e
135
+ else:
136
+ # uncompressed payload
137
+ consumer.write(self._payload)
138
+ return succeed(None)
139
+
140
+ def pauseProducing(self):
141
+ pass
142
+
143
+ def stopProducing(self):
144
+ pass
145
+
146
+ def _getGzipUncompressedSize(self) -> int:
147
+ # Uncompressed size is stored in the last 4 bytes of the gzip file.
148
+ # We can read the binary data and convert it to an int.
149
+ # (This will only work for files under 4GB)
150
+ with BytesIO(self._payload) as f:
151
+ try:
152
+ return struct.unpack("I", self._payload[-4:])[0]
153
+ except Exception as e:
154
+ # may be invalid format of gzip
155
+ logger.exception(e)
156
+ return UNKNOWN_LENGTH
157
+
158
+
159
+ class _ResponseProducer(Protocol):
160
+ def __init__(self, meta: Type[Tuple]):
161
+ self._finishedDeferred = Deferred()
162
+ self._writeSize = 0
163
+ self._body = b""
164
+ self._meta = meta
165
+
166
+ @property
167
+ def asyncData(self):
168
+ return self._finishedDeferred
169
+
170
+ @property
171
+ def meta(self):
172
+ return self._meta
173
+
174
+ def dataReceived(self, data: bytes):
175
+ logger.debug(bytes)
176
+ self._body += data
177
+
178
+ def connectionLost(self, reason):
179
+ if isinstance(reason.value, ResponseDone):
180
+ self._meta.responseDate = datetime.now(tz=pytz.utc)
181
+ self._meta.body = self._body
@@ -0,0 +1,107 @@
1
+ import json
2
+ import logging
3
+ from typing import Union, Type, runtime_checkable
4
+
5
+ from twisted.internet import reactor
6
+ from twisted.internet.defer import Deferred, inlineCallbacks
7
+ from twisted.internet.protocol import Protocol
8
+ from twisted.web._newclient import ResponseDone
9
+ from twisted.web.client import Agent, GzipDecoder, ContentDecoderAgent
10
+ from twisted.web.http_headers import Headers
11
+ from zope.interface import implementer
12
+ from twisted.internet.defer import succeed
13
+ from twisted.web.iweb import IBodyProducer
14
+
15
+ from vortex.Tuple import Tuple
16
+ from vortex.restful.RestfulResource import HTTP_REQUEST
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class RestfulHttpClient:
22
+ def __init__(
23
+ self,
24
+ url: Union[str, bytes],
25
+ method: HTTP_REQUEST,
26
+ postTuple: Tuple,
27
+ ResponseTuple: Type[Tuple],
28
+ ):
29
+ self._httpMethod = method.value.encode()
30
+ self._url = url.encode() if isinstance(url, str) else url
31
+ self._postTuple = postTuple
32
+ self._ResponseTuple = ResponseTuple
33
+
34
+ @inlineCallbacks
35
+ def run(self):
36
+ # convert tuple to json
37
+ body = self._postTuple.tupleToRestfulJsonDict()
38
+ body = json.dumps(body).encode("utf-8")
39
+
40
+ # add http headers
41
+ headers = Headers(
42
+ {
43
+ "User-Agent": ["synerty/1.0"],
44
+ "Content-Type": ["application/json"],
45
+ }
46
+ )
47
+
48
+ # Add the gzip decoder
49
+ agent = ContentDecoderAgent(Agent(reactor), [(b"gzip", GzipDecoder)])
50
+
51
+ # Make the web request
52
+ response = yield agent.request(
53
+ self._httpMethod, self._url, headers, _BytesProducer(body)
54
+ )
55
+
56
+ # Get the response data
57
+ responseData = yield self._cbResponse(response)
58
+
59
+ # Convert the bytes into a tuple and return
60
+ return self._parseTuple(responseData)
61
+
62
+ def _cbResponse(self, response):
63
+ bodyDownloader = _RestfulBody()
64
+ response.deliverBody(bodyDownloader)
65
+ return bodyDownloader.asyncData
66
+
67
+ def _parseTuple(self, bytes_):
68
+ json_ = json.loads(bytes_.decode("utf-8"))
69
+ return Tuple.restfulJsonDictToTupleWithValidation(
70
+ json_, self._ResponseTuple
71
+ )
72
+
73
+
74
+ @implementer(IBodyProducer)
75
+ class _BytesProducer:
76
+ def __init__(self, body):
77
+ self.body = body
78
+ self.length = len(body)
79
+
80
+ def startProducing(self, consumer):
81
+ consumer.write(self.body)
82
+ return succeed(None)
83
+
84
+ def pauseProducing(self):
85
+ pass
86
+
87
+ def stopProducing(self):
88
+ pass
89
+
90
+
91
+ class _RestfulBody(Protocol):
92
+ def __init__(self):
93
+ self._finishedDeferred = Deferred()
94
+ self._writeSize = 0
95
+ self._body = b""
96
+
97
+ @property
98
+ def asyncData(self):
99
+ return self._finishedDeferred
100
+
101
+ def dataReceived(self, data: bytes):
102
+ logger.debug(bytes)
103
+ self._body += data
104
+
105
+ def connectionLost(self, reason):
106
+ if isinstance(reason.value, ResponseDone):
107
+ self._finishedDeferred.callback(self._body)
@@ -0,0 +1,60 @@
1
+ import json
2
+ import logging
3
+ from typing import Annotated, List
4
+
5
+ from twisted.trial import unittest
6
+
7
+ from vortex.Tuple import (
8
+ Tuple,
9
+ TupleField,
10
+ addTupleType,
11
+ IntTupleFieldValidator,
12
+ TupleFieldValidatorABC,
13
+ )
14
+ from vortex.restful.RestfulHttpClient import RestfulHttpClient
15
+ from vortex.restful.RestfulResource import HTTP_REQUEST
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @addTupleType
21
+ class TestTuple(Tuple):
22
+ __tupleType__ = "TestTuple"
23
+ id: int = TupleField()
24
+ count: Annotated[int, IntTupleFieldValidator(0, 100)] = TupleField()
25
+ text: str = TupleField()
26
+
27
+
28
+ @addTupleType
29
+ class ResponseTuple(Tuple):
30
+ __tupleType__ = "ResponseTuple"
31
+ data: str = TupleField(comment="request in string")
32
+ json: TestTuple = TupleField(comment="request converted to jsonDict")
33
+
34
+
35
+ class RestfulClientTest(unittest.TestCase):
36
+ def setUp(self):
37
+ self.tuple = TestTuple()
38
+ self.tuple.id = 10
39
+ self.tuple.count = 50
40
+ self.tuple.text = "test"
41
+
42
+ def testHttpPost(self):
43
+ client = RestfulHttpClient(
44
+ "http://httpbin.org/anything",
45
+ method=HTTP_REQUEST.POST,
46
+ postTuple=self.tuple,
47
+ ResponseTuple=ResponseTuple,
48
+ )
49
+ d = client.run()
50
+ d.addCallback(self._checkPostJSON)
51
+ # d.addCallback(self.print)
52
+ return d
53
+
54
+ def _checkPostJSON(self, tuple_):
55
+ self.assertTrue(isinstance(tuple_, ResponseTuple))
56
+ self.assertEqual(
57
+ tuple_.data, json.dumps(self.tuple.tupleToRestfulJsonDict())
58
+ )
59
+ self.assertEqual(tuple_.json.id, self.tuple.id)
60
+ self.assertEqual(tuple_.json.text, self.tuple.text)
@@ -0,0 +1,153 @@
1
+ import json
2
+ import logging
3
+ from enum import Enum
4
+ from typing import Callable
5
+ from typing import List
6
+
7
+ from twisted.internet.defer import inlineCallbacks
8
+ from twisted.python.compat import nativeString
9
+ from twisted.web.server import NOT_DONE_YET
10
+ from txhttputil.site.BasicResource import BasicResource
11
+ from vortex import Tuple
12
+ from vortex.DeferUtil import deferToThreadWrapWithLogger
13
+ from vortex.DeferUtil import vortexLogFailure
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class HTTP_REQUEST(Enum):
19
+ GET = "GET"
20
+ POST = "POST"
21
+ PUT = "PUT"
22
+ DELETE = "DELETE"
23
+
24
+
25
+ class _JsonResource(BasicResource):
26
+ isLeaf = True
27
+ isGzipped = True
28
+
29
+ def __init__(self, handler: Callable, TupleClass: Tuple):
30
+ BasicResource.__init__(self)
31
+
32
+ self._handler = handler
33
+ self._tupleClass = TupleClass
34
+
35
+ @deferToThreadWrapWithLogger(logger)
36
+ def _requestToTuple(self, request) -> Tuple:
37
+ req = json.load(request.content)
38
+ tuple_ = self._tupleClass()
39
+ tuple_.fromJsonDict(req)
40
+ return tuple_
41
+
42
+ @deferToThreadWrapWithLogger(logger)
43
+ def _tupleToResponse(self, tuple_: Tuple) -> bytes:
44
+ dict_ = tuple_.tupleToRestfulJsonDict()
45
+ json_ = json.dumps(dict_)
46
+ return json_.encode()
47
+
48
+ def _writeSuccessResponse(self, response: bytes, request):
49
+ request.setResponseCode(200)
50
+ request.setHeader("Content-Type", "application/json")
51
+ request.write(response)
52
+ request.finish()
53
+
54
+ def _writeErrorResponse(self, request):
55
+ request.setResponseCode(500)
56
+ request.setHeader("Content-Type", "application/json")
57
+ request.write(b'{"error":"internal error"}')
58
+ request.finish()
59
+
60
+ @inlineCallbacks
61
+ def _renderAsync(self, request):
62
+ tupleIn = None
63
+ try:
64
+ tupleIn = yield self._requestToTuple(request)
65
+ tupleOut = yield self._handler(tupleIn)
66
+ response = yield self._tupleToResponse(tupleOut)
67
+ self._writeSuccessResponse(response, request)
68
+
69
+ except Exception as e:
70
+ self._writeErrorResponse(request)
71
+ logger.debug(
72
+ f"Error while processing REST Tuple\n{tupleIn}\n{request.path}"
73
+ )
74
+ logger.exception(e)
75
+
76
+ def render(self, request):
77
+ d = self._renderAsync(request)
78
+ d.addErrback(vortexLogFailure, logger=logger, consumeError=True)
79
+
80
+ return NOT_DONE_YET
81
+
82
+
83
+ class ErrorJsonResource(BasicResource):
84
+ isLeaf = True
85
+ isGzipped = True
86
+
87
+ def __init__(self, errorCode: int):
88
+ self._errorCode = errorCode
89
+
90
+ def render(self, request):
91
+ request.setResponseCode(self._errorCode)
92
+ return b""
93
+
94
+
95
+ class PluginRestfulResource(BasicResource):
96
+ def __init__(self):
97
+ BasicResource.__init__(self)
98
+ self._registeredMethods = {}
99
+
100
+ def registerMethod(
101
+ self,
102
+ handlerFunction: Callable,
103
+ TupleClass: Tuple,
104
+ urlPrefix: bytes,
105
+ registeredRequestMethod: List[HTTP_REQUEST],
106
+ ):
107
+ if urlPrefix in self._registeredMethods:
108
+ raise ValueError(f'Route "{urlPrefix}" already exists')
109
+ if not callable(handlerFunction):
110
+ raise TypeError(f'"{str(handlerFunction)}" is not a callable')
111
+
112
+ # register handler as partial function (poor man's Template<type>)
113
+ # as callback function whose input is an instance of a Tuple
114
+ self._registeredMethods[urlPrefix] = {
115
+ "handler": handlerFunction,
116
+ "allowedMethods": set(
117
+ [method.value for method in registeredRequestMethod]
118
+ ),
119
+ "tupleClass": TupleClass,
120
+ }
121
+
122
+ def getChild(self, path, request):
123
+ """Get requests routed to an handler
124
+
125
+ Routes to a registered handler to process the request.
126
+ This function routes resources to handlers which eventually returns
127
+ JSON response resource in json. Everything returned here should be
128
+ final.
129
+
130
+ Once url prefix for a resource is matched, it checks the
131
+ request method is allowed. If granted, it delegates the request to the
132
+ registered handler to process the request. The handler should convert
133
+ request to a Tuple and should respond a tuple.
134
+
135
+ :param path: route url for a resource, with plugin name prefix stripped
136
+ :param request: twisted.web.http.Request
137
+ :return: twisted.web.resource.Resource
138
+ """
139
+ # route check
140
+ if path not in self._registeredMethods.keys():
141
+ return ErrorJsonResource(404)
142
+
143
+ # request method check
144
+ requestMethod = nativeString(request.method)
145
+ if requestMethod not in self._registeredMethods[path]["allowedMethods"]:
146
+ return ErrorJsonResource(403)
147
+
148
+ # invoke resource handler
149
+ TupleClass = self._registeredMethods[path]["tupleClass"]
150
+
151
+ handler = self._registeredMethods[path]["handler"]
152
+
153
+ return _JsonResource(handler, TupleClass)
@@ -0,0 +1,100 @@
1
+ import io
2
+ import json
3
+ import logging
4
+
5
+ from vortex.restful.RestfulResource import HTTP_REQUEST
6
+ from vortex.restful.RestfulResource import PluginRestfulResource
7
+ from twisted.trial import unittest
8
+ from twisted.web.test.requesthelper import DummyRequest
9
+ from vortex.Tuple import Tuple
10
+ from vortex.Tuple import TupleField
11
+ from vortex.Tuple import addTupleType
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @addTupleType
17
+ class DummyTuple(Tuple):
18
+ __tupleType__ = "DummyTuple"
19
+
20
+ id = TupleField()
21
+ text = TupleField()
22
+
23
+
24
+ class TestHandlerController:
25
+ def testMethod(self, tuple_: DummyTuple):
26
+ if hasattr(tuple_, "tupleToRestfulJsonDict"):
27
+ # vortexpy > 2.1.3
28
+ return tuple_.tupleToRestfulJsonDict()
29
+ else:
30
+ return tuple_.tupleToSmallJsonDict()
31
+
32
+
33
+ class RestfulServerTest(unittest.TestCase):
34
+ def setUp(self):
35
+ handlerController = TestHandlerController()
36
+ self.pluginAResource = PluginRestfulResource()
37
+ self.pluginAResource.registerMethod(
38
+ handlerController.testMethod,
39
+ DummyTuple,
40
+ b"test",
41
+ [HTTP_REQUEST.GET, HTTP_REQUEST.POST],
42
+ )
43
+ self.pluginAResource.registerMethod(
44
+ handlerController.testMethod,
45
+ DummyTuple,
46
+ b"test/test",
47
+ [HTTP_REQUEST.GET, HTTP_REQUEST.POST],
48
+ )
49
+ self.pluginAResource.registerMethod(
50
+ handlerController.testMethod,
51
+ DummyTuple,
52
+ b"test/test/test",
53
+ [HTTP_REQUEST.GET, HTTP_REQUEST.POST],
54
+ )
55
+
56
+ def _dictToBytes(self, dictionary: dict) -> bytes:
57
+ return bytes(json.dumps(dictionary), "utf-8")
58
+
59
+ def _bytesToDict(self, bytes_: bytes) -> dict:
60
+ string = bytes_[0].decode("utf-8")
61
+ return json.loads(string)
62
+
63
+ def _check(self, expected: dict, actual: dict) -> bool:
64
+ # expected dict should be equal or a subnet of actual dict
65
+ return expected.items() <= actual.items()
66
+
67
+ def testValidJsonRequest(self):
68
+ requestDict = {"id": 1, "text": "text"}
69
+
70
+ request = DummyRequest([])
71
+ request.content = io.BytesIO(self._dictToBytes(requestDict))
72
+
73
+ jsonResource = self.pluginAResource.getChild(b"test", request)
74
+
75
+ jsonResource.render(request)
76
+ self.assertEqual(request.responseCode, 200)
77
+ responseDict = self._bytesToDict(request.written)
78
+ self.assertTrue(self._check(requestDict, responseDict))
79
+
80
+ def testInvalidJsonRequest(self):
81
+ request = DummyRequest([])
82
+ request.content = io.BytesIO(b"##invalid json}")
83
+
84
+ jsonResource = self.pluginAResource.getChild(b"test", request)
85
+ jsonResource.render(request)
86
+ self.assertEqual(request.responseCode, 500)
87
+
88
+ def testPath(self):
89
+ requestDict = {"id": 1, "text": "text"}
90
+
91
+ for path in [b"test", b"test/test", b"test/test/test"]:
92
+ request = DummyRequest([])
93
+ request.content = io.BytesIO(self._dictToBytes(requestDict))
94
+
95
+ jsonResource = self.pluginAResource.getChild(path, request)
96
+
97
+ jsonResource.render(request)
98
+ self.assertEqual(request.responseCode, 200)
99
+ responseDict = self._bytesToDict(request.written)
100
+ self.assertTrue(self._check(requestDict, responseDict))
File without changes