aspyx-service 0.10.2__tar.gz → 0.10.3__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.

Potentially problematic release.


This version of aspyx-service might be problematic. Click here for more details.

Files changed (21) hide show
  1. {aspyx_service-0.10.2 → aspyx_service-0.10.3}/PKG-INFO +11 -7
  2. {aspyx_service-0.10.2 → aspyx_service-0.10.3}/README.md +10 -6
  3. aspyx_service-0.10.3/performance_tests/performance-test.py +205 -0
  4. {aspyx_service-0.10.2 → aspyx_service-0.10.3}/pyproject.toml +1 -1
  5. {aspyx_service-0.10.2 → aspyx_service-0.10.3}/src/aspyx_service/channels.py +18 -13
  6. {aspyx_service-0.10.2 → aspyx_service-0.10.3}/src/aspyx_service/restchannel.py +10 -11
  7. {aspyx_service-0.10.2 → aspyx_service-0.10.3}/src/aspyx_service/serialization.py +3 -0
  8. {aspyx_service-0.10.2 → aspyx_service-0.10.3}/src/aspyx_service/service.py +8 -7
  9. aspyx_service-0.10.3/tests/__init__.py +0 -0
  10. {aspyx_service-0.10.2 → aspyx_service-0.10.3}/tests/common.py +5 -0
  11. {aspyx_service-0.10.2 → aspyx_service-0.10.3}/tests/test_async_service.py +6 -8
  12. aspyx_service-0.10.3/tests/test_serialization.py +69 -0
  13. {aspyx_service-0.10.2 → aspyx_service-0.10.3}/tests/test_service.py +5 -5
  14. aspyx_service-0.10.2/tests/__init__.py +0 -1
  15. {aspyx_service-0.10.2 → aspyx_service-0.10.3}/.gitignore +0 -0
  16. {aspyx_service-0.10.2 → aspyx_service-0.10.3}/LICENSE +0 -0
  17. {aspyx_service-0.10.2 → aspyx_service-0.10.3}/src/aspyx_service/__init__.py +0 -0
  18. {aspyx_service-0.10.2 → aspyx_service-0.10.3}/src/aspyx_service/healthcheck.py +0 -0
  19. {aspyx_service-0.10.2 → aspyx_service-0.10.3}/src/aspyx_service/registries.py +0 -0
  20. {aspyx_service-0.10.2 → aspyx_service-0.10.3}/src/aspyx_service/server.py +0 -0
  21. {aspyx_service-0.10.2 → aspyx_service-0.10.3}/tests/config.yaml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aspyx_service
3
- Version: 0.10.2
3
+ Version: 0.10.3
4
4
  Summary: Aspyx Service framework
5
5
  Author-email: Andreas Ernst <andreas.ernst7@gmail.com>
6
6
  License: MIT License
@@ -70,19 +70,23 @@ that lets you deploy, discover and call services with different remoting protoco
70
70
 
71
71
  The basic design consists of four different concepts:
72
72
 
73
- !!! info "Service"
73
+ **Service**
74
+
74
75
  defines a group of methods that can be called either locally or remotely.
75
76
  These methods represent the functional interface exposed to clients — similar to an interface in traditional programming
76
77
 
77
- !!! info "Component"
78
+ **Component**
79
+
78
80
  a component bundles one or more services and declares the channels (protocols) used to expose them.
79
81
  Think of a component as a deployment unit or module.
80
82
 
81
- !!! info "Component Registry "
83
+ **Component Registry**
84
+
82
85
  acts as the central directory for managing available components.
83
86
  It allows the framework to register, discover, and resolve components and their services.
84
87
 
85
- !!! info "Channel"
88
+ **Channel**
89
+
86
90
  is a pluggable transport layer that defines how service method invocations are transmitted and handled.
87
91
 
88
92
  Let's look at the "interface" layer first.
@@ -111,7 +115,7 @@ class Module:
111
115
 
112
116
  @create()
113
117
  def create_registry(self) -> ConsulComponentRegistry:
114
- return ConsulComponentRegistry(Server.port, "http://localhost:8500") # a consul based registry!
118
+ return ConsulComponentRegistry(Server.port, Consul(host="localhost", port=8500)) # a consul based registry!
115
119
 
116
120
  environment = Environment(Module)
117
121
  service_manager = environment.get(ServiceManager)
@@ -361,7 +365,7 @@ A so called `URLSelector` is used internally to provide URLs for every single ca
361
365
  - `FirstURLSelector` always returns the first URL of the list of possible URLs
362
366
  - `RoundRobinURLSelector` switches sequentially between all URLs.
363
367
 
364
- To customize the behavior, an around advice can be implemented easily:
368
+ To customize the behavior, an `around` advice can be implemented easily:
365
369
 
366
370
  **Example**:
367
371
 
@@ -34,19 +34,23 @@ that lets you deploy, discover and call services with different remoting protoco
34
34
 
35
35
  The basic design consists of four different concepts:
36
36
 
37
- !!! info "Service"
37
+ **Service**
38
+
38
39
  defines a group of methods that can be called either locally or remotely.
39
40
  These methods represent the functional interface exposed to clients — similar to an interface in traditional programming
40
41
 
41
- !!! info "Component"
42
+ **Component**
43
+
42
44
  a component bundles one or more services and declares the channels (protocols) used to expose them.
43
45
  Think of a component as a deployment unit or module.
44
46
 
45
- !!! info "Component Registry "
47
+ **Component Registry**
48
+
46
49
  acts as the central directory for managing available components.
47
50
  It allows the framework to register, discover, and resolve components and their services.
48
51
 
49
- !!! info "Channel"
52
+ **Channel**
53
+
50
54
  is a pluggable transport layer that defines how service method invocations are transmitted and handled.
51
55
 
52
56
  Let's look at the "interface" layer first.
@@ -75,7 +79,7 @@ class Module:
75
79
 
76
80
  @create()
77
81
  def create_registry(self) -> ConsulComponentRegistry:
78
- return ConsulComponentRegistry(Server.port, "http://localhost:8500") # a consul based registry!
82
+ return ConsulComponentRegistry(Server.port, Consul(host="localhost", port=8500)) # a consul based registry!
79
83
 
80
84
  environment = Environment(Module)
81
85
  service_manager = environment.get(ServiceManager)
@@ -325,7 +329,7 @@ A so called `URLSelector` is used internally to provide URLs for every single ca
325
329
  - `FirstURLSelector` always returns the first URL of the list of possible URLs
326
330
  - `RoundRobinURLSelector` switches sequentially between all URLs.
327
331
 
328
- To customize the behavior, an around advice can be implemented easily:
332
+ To customize the behavior, an `around` advice can be implemented easily:
329
333
 
330
334
  **Example**:
331
335
 
@@ -0,0 +1,205 @@
1
+ """
2
+ Tests
3
+ """
4
+ import asyncio
5
+ import logging
6
+ import threading
7
+ import time
8
+
9
+ from typing import Callable, TypeVar, Type, Awaitable, Any
10
+
11
+ from packages.aspyx_service.tests.common import service_manager, TestService, TestRestService, Pydantic, Data, \
12
+ TestAsyncRestService, TestAsyncService
13
+ from packages.aspyx_service.tests.test_async_service import data
14
+
15
+ T = TypeVar("T")
16
+
17
+ # main
18
+
19
+ pydantic = Pydantic(i=1, f=1.0, b=True, s="s")
20
+ data = Data(i=1, f=1.0, b=True, s="s")
21
+
22
+ def run_loops(name: str, loops: int, type: Type[T], instance: T, callable: Callable[[T], None]):
23
+ start = time.perf_counter()
24
+ for _ in range(loops):
25
+ callable(instance)
26
+
27
+ end = time.perf_counter()
28
+ avg_ms = ((end - start) / loops) * 1000
29
+
30
+ print(f"run {name}, loops={loops}: avg={avg_ms:.3f} ms")
31
+
32
+ async def run_async_loops(name: str, loops: int, type: Type[T], instance: T, callable: Callable[[T], Awaitable[Any]]):
33
+ start = time.perf_counter()
34
+ for _ in range(loops):
35
+ await callable(instance)
36
+
37
+ end = time.perf_counter()
38
+ avg_ms = ((end - start) / loops) * 1000
39
+
40
+ print(f"run {name}, loops={loops}: avg={avg_ms:.3f} ms")
41
+
42
+ def run_threaded_async_loops(name: str, loops: int, n_threads: int, type: Type[T], instance: T, callable: Callable[[T], Awaitable[Any]]):
43
+ threads = []
44
+
45
+ def worker(thread_id: int):
46
+ loop = asyncio.new_event_loop()
47
+ asyncio.set_event_loop(loop)
48
+
49
+ async def run():
50
+ for i in range(loops):
51
+ await callable(instance)
52
+
53
+ loop.run_until_complete(run())
54
+ loop.close()
55
+
56
+ start = time.perf_counter()
57
+
58
+ for t_id in range(0, n_threads):
59
+ thread = threading.Thread(target=worker, args=(t_id,))
60
+ threads.append(thread)
61
+ thread.start()
62
+
63
+ for thread in threads:
64
+ thread.join()
65
+
66
+ end = time.perf_counter()
67
+ took = (end - start) * 1000
68
+ avg_ms = ((end - start) / (n_threads * loops)) * 1000
69
+
70
+ print(f"{name} {loops} in {n_threads} threads: {took} ms, avg: {avg_ms}ms")
71
+
72
+ def run_threaded_sync_loops(name: str, loops: int, n_threads: int, type: Type[T], instance: T, callable: Callable[[T], Any]):
73
+ threads = []
74
+
75
+ def worker(thread_id: int):
76
+ loop = asyncio.new_event_loop()
77
+ asyncio.set_event_loop(loop)
78
+
79
+ async def run():
80
+ for i in range(loops):
81
+ callable(instance)
82
+
83
+ loop.run_until_complete(run())
84
+ loop.close()
85
+
86
+ start = time.perf_counter()
87
+
88
+ for t_id in range(0, n_threads):
89
+ thread = threading.Thread(target=worker, args=(t_id,))
90
+ threads.append(thread)
91
+ thread.start()
92
+
93
+ for thread in threads:
94
+ thread.join()
95
+
96
+ end = time.perf_counter()
97
+ took = (end - start) * 1000
98
+ avg_ms = ((end - start) / (n_threads * loops)) * 1000
99
+
100
+ print(f"{name} {loops} in {n_threads} threads: {took} ms, avg: {avg_ms}ms")
101
+
102
+ async def main():
103
+ # get service manager
104
+
105
+ manager = service_manager()
106
+ loops = 1000
107
+
108
+ # tests
109
+
110
+ run_loops("rest", loops, TestRestService, manager.get_service(TestRestService, preferred_channel="rest"), lambda service: service.get("world"))
111
+ run_loops("json", loops, TestService, manager.get_service(TestService, preferred_channel="dispatch-json"), lambda service: service.hello("world"))
112
+ run_loops("msgpack", loops, TestService, manager.get_service(TestService, preferred_channel="dispatch-msgpack"), lambda service: service.hello("world"))
113
+
114
+ # pydantic
115
+
116
+ run_loops("rest & pydantic", loops, TestRestService, manager.get_service(TestRestService, preferred_channel="rest"), lambda service: service.post_pydantic("hello", pydantic))
117
+ run_loops("json & pydantic", loops, TestService, manager.get_service(TestService, preferred_channel="dispatch-json"), lambda service: service.pydantic(pydantic))
118
+ run_loops("msgpack & pydantic", loops, TestService, manager.get_service(TestService, preferred_channel="dispatch-msgpack"), lambda service: service.pydantic(pydantic))
119
+
120
+ # data class
121
+
122
+ run_loops("rest & data", loops, TestRestService, manager.get_service(TestRestService, preferred_channel="rest"),
123
+ lambda service: service.post_data("hello", data))
124
+ run_loops("json & data", loops, TestService,
125
+ manager.get_service(TestService, preferred_channel="dispatch-json"),
126
+ lambda service: service.data(data))
127
+ run_loops("msgpack & data", loops, TestService,
128
+ manager.get_service(TestService, preferred_channel="dispatch-msgpack"),
129
+ lambda service: service.data(data))
130
+
131
+ # async
132
+
133
+ await run_async_loops("async rest", loops, TestAsyncRestService, manager.get_service(TestAsyncRestService, preferred_channel="rest"),
134
+ lambda service: service.get("world"))
135
+ await run_async_loops("async json", loops, TestAsyncService, manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
136
+ lambda service: service.hello("world"))
137
+ await run_async_loops("async msgpack", loops, TestAsyncService, manager.get_service(TestAsyncService, preferred_channel="dispatch-msgpack"),
138
+ lambda service: service.hello("world"))
139
+
140
+ # pydantic
141
+
142
+ await run_async_loops("async rest & pydantic", loops, TestAsyncRestService, manager.get_service(TestAsyncRestService, preferred_channel="rest"),
143
+ lambda service: service.post_pydantic("hello", pydantic))
144
+ await run_async_loops("async json & pydantic", loops, TestAsyncService,
145
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
146
+ lambda service: service.pydantic(pydantic))
147
+ await run_async_loops("async msgpack & pydantic", loops, TestAsyncService,
148
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-msgpack"),
149
+ lambda service: service.pydantic(pydantic))
150
+
151
+ # data class
152
+
153
+ # pydantic
154
+
155
+ await run_async_loops("async rest & data", loops, TestAsyncRestService, manager.get_service(TestAsyncRestService, preferred_channel="rest"),
156
+ lambda service: service.post_data("hello", data))
157
+ await run_async_loops("async json & data", loops, TestAsyncService,
158
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
159
+ lambda service: service.data(data))
160
+ await run_async_loops("async msgpack & data", loops, TestAsyncService,
161
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-msgpack"),
162
+ lambda service: service.data(data))
163
+
164
+ # a real thread test
165
+
166
+ # sync
167
+
168
+ run_threaded_sync_loops("threaded sync json, 1 thread", loops, 1, TestService,
169
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
170
+ lambda service: service.hello("world"))
171
+ run_threaded_sync_loops("threaded sync json, 2 thread", loops, 2, TestService,
172
+ manager.get_service(TestService, preferred_channel="dispatch-json"),
173
+ lambda service: service.hello("world"))
174
+ run_threaded_sync_loops("threaded sync json, 4 thread", loops, 4, TestService,
175
+ manager.get_service(TestService, preferred_channel="dispatch-json"),
176
+ lambda service: service.hello("world"))
177
+ run_threaded_sync_loops("threaded sync json, 8 thread", loops, 8, TestService,
178
+ manager.get_service(TestService, preferred_channel="dispatch-json"),
179
+ lambda service: service.hello("world"))
180
+ run_threaded_sync_loops("threaded sync json, 16 thread", loops, 16, TestService,
181
+ manager.get_service(TestService, preferred_channel="dispatch-json"),
182
+ lambda service: service.hello("world"))
183
+
184
+ # async
185
+
186
+ run_threaded_async_loops("threaded async json, 1 thread", loops, 1, TestAsyncService,
187
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
188
+ lambda service: service.hello("world") )
189
+ run_threaded_async_loops("threaded async json, 2 thread", loops, 2, TestAsyncService,
190
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
191
+ lambda service: service.hello("world"))
192
+ run_threaded_async_loops("threaded async json, 4 thread", loops, 4, TestAsyncService,
193
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
194
+ lambda service: service.hello("world"))
195
+ run_threaded_async_loops("threaded async json, 8 thread", loops, 8, TestAsyncService,
196
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
197
+ lambda service: service.hello("world"))
198
+ run_threaded_async_loops("threaded async json, 16 thread", loops, 16, TestAsyncService,
199
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
200
+ lambda service: service.hello("world"))
201
+
202
+
203
+ if __name__ == "__main__":
204
+ asyncio.run(main())
205
+
@@ -2,7 +2,7 @@
2
2
 
3
3
  [project]
4
4
  name = "aspyx_service"
5
- version = "0.10.2"
5
+ version = "0.10.3"
6
6
  description = "Aspyx Service framework"
7
7
  authors = [{ name = "Andreas Ernst", email = "andreas.ernst7@gmail.com" }]
8
8
  readme = "README.md"
@@ -13,6 +13,7 @@ from pydantic import BaseModel
13
13
 
14
14
  from aspyx.di.configuration import inject_value
15
15
  from aspyx.reflection import DynamicProxy, TypeDescriptor
16
+ from aspyx.threading import ThreadLocal
16
17
  from .service import ServiceManager, ServiceCommunicationException
17
18
 
18
19
  from .service import ComponentDescriptor, ChannelInstances, ServiceException, channel, Channel, RemoteServiceException
@@ -28,6 +29,11 @@ class HTTPXChannel(Channel):
28
29
  "timeout"
29
30
  ]
30
31
 
32
+ # class properties
33
+
34
+ client_local = ThreadLocal[Client]()
35
+ async_client_local = ThreadLocal[AsyncClient]()
36
+
31
37
  # class methods
32
38
 
33
39
  @classmethod
@@ -61,8 +67,6 @@ class HTTPXChannel(Channel):
61
67
  super().__init__()
62
68
 
63
69
  self.timeout = 1000.0
64
- self.client: Optional[Client] = None
65
- self.async_client: Optional[AsyncClient] = None
66
70
  self.service_names: dict[Type, str] = {}
67
71
  self.deserializers: dict[Callable, Callable] = {}
68
72
 
@@ -95,24 +99,25 @@ class HTTPXChannel(Channel):
95
99
  for service in component_descriptor.services:
96
100
  self.service_names[service.type] = service.name
97
101
 
98
- # make client
99
-
100
- self.client = self.make_client()
101
- self.async_client = self.make_async_client()
102
-
103
102
  # public
104
103
 
105
104
  def get_client(self) -> Client:
106
- if self.client is None:
107
- self.client = self.make_client()
105
+ client = self.client_local.get()
106
+
107
+ if client is None:
108
+ client = self.make_client()
109
+ self.client_local.set(client)
108
110
 
109
- return self.client
111
+ return client
110
112
 
111
113
  def get_async_client(self) -> AsyncClient:
112
- if self.async_client is None:
113
- self.async_client = self.make_async_client()
114
+ async_client = self.async_client_local.get()
115
+
116
+ if async_client is None:
117
+ async_client = self.make_async_client()
118
+ self.async_client_local.set(async_client)
114
119
 
115
- return self.async_client
120
+ return async_client
116
121
 
117
122
  def make_client(self) -> Client:
118
123
  return Client() # base_url=url
@@ -249,16 +249,15 @@ class RestChannel(HTTPXChannel):
249
249
  try:
250
250
  result = None
251
251
  if call.type == "get":
252
- result = await self.get_client().get(self.get_url() + url, params=query_params, timeout=self.timeout).json()
252
+ result = await self.get_async_client().get(self.get_url() + url, params=query_params, timeout=self.timeout)
253
253
  elif call.type == "put":
254
- result = await self.get_client().put(self.get_url() + url, params=query_params, timeout=self.timeout).json()
254
+ result = await self.get_async_client().put(self.get_url() + url, params=query_params, timeout=self.timeout)
255
255
  elif call.type == "delete":
256
- result = await self.get_client().delete(self.get_url() + url, params=query_params, timeout=self.timeout).json()
256
+ result = await self.get_async_client().delete(self.get_url() + url, params=query_params, timeout=self.timeout)
257
257
  elif call.type == "post":
258
- result = await self.get_client().post(self.get_url() + url, params=query_params, json=body,
259
- timeout=self.timeout).json()
258
+ result = await self.get_async_client().post(self.get_url() + url, params=query_params, json=body, timeout=self.timeout)
260
259
 
261
- return self.get_deserializer(invocation.type, invocation.method)(result)
260
+ return self.get_deserializer(invocation.type, invocation.method)(result.json())
262
261
  except ServiceCommunicationException:
263
262
  raise
264
263
 
@@ -285,15 +284,15 @@ class RestChannel(HTTPXChannel):
285
284
  try:
286
285
  result = None
287
286
  if call.type == "get":
288
- result = self.get_client().get(self.get_url() + url, params=query_params, timeout=self.timeout).json()
287
+ result = self.get_client().get(self.get_url() + url, params=query_params, timeout=self.timeout)
289
288
  elif call.type == "put":
290
- result = self.get_client().put(self.get_url() + url, params=query_params, timeout=self.timeout).json()
289
+ result = self.get_client().put(self.get_url() + url, params=query_params, timeout=self.timeout)
291
290
  elif call.type == "delete":
292
- result = self.get_client().delete(self.get_url() + url, params=query_params, timeout=self.timeout).json()
291
+ result = self.get_client().delete(self.get_url() + url, params=query_params, timeout=self.timeout)
293
292
  elif call.type == "post":
294
- result = self.get_client().post(self.get_url() + url, params=query_params, json=body, timeout=self.timeout).json()
293
+ result = self.get_client().post(self.get_url() + url, params=query_params, json=body, timeout=self.timeout)
295
294
 
296
- return self.get_deserializer(invocation.type, invocation.method)(result)
295
+ return self.get_deserializer(invocation.type, invocation.method)(result.json())
297
296
  except ServiceCommunicationException:
298
297
  raise
299
298
 
@@ -42,6 +42,9 @@ class TypeDeserializer:
42
42
  if is_dataclass(typ):
43
43
  field_deserializers = {f.name: TypeDeserializer(f.type) for f in fields(typ)}
44
44
  def deser_dataclass(value):
45
+ if is_dataclass(value):
46
+ return value
47
+
45
48
  return typ(**{
46
49
  k: field_deserializers[k](v) for k, v in value.items()
47
50
  })
@@ -769,11 +769,12 @@ class ServiceManager:
769
769
 
770
770
  # check proxy
771
771
 
772
- key = TypeAndChannel(type=component_descriptor.type, channel=preferred_channel)
772
+ channel_key = TypeAndChannel(type=component_descriptor.type, channel=preferred_channel)
773
+ proxy_key = TypeAndChannel(type=service_type, channel=preferred_channel)
773
774
 
774
- proxy = self.proxy_cache.get(key, None)
775
+ proxy = self.proxy_cache.get(proxy_key, None)
775
776
  if proxy is None:
776
- channel_instance = self.channel_cache.get(key, None)
777
+ channel_instance = self.channel_cache.get(channel_key, None)
777
778
 
778
779
  if channel_instance is None:
779
780
  address = self.find_service_address(component_descriptor, preferred_channel)
@@ -786,9 +787,9 @@ class ServiceManager:
786
787
  # channel may have changed
787
788
 
788
789
  if address.channel != preferred_channel:
789
- key = TypeAndChannel(type=component_descriptor.type, channel=address.channel)
790
+ channel_key = TypeAndChannel(type=component_descriptor.type, channel=address.channel)
790
791
 
791
- channel_instance = self.channel_cache.get(key, None)
792
+ channel_instance = self.channel_cache.get(channel_key, None)
792
793
  if channel_instance is None:
793
794
  # create channel
794
795
 
@@ -796,7 +797,7 @@ class ServiceManager:
796
797
 
797
798
  # cache
798
799
 
799
- self.channel_cache[key] = channel_instance
800
+ self.channel_cache[channel_key] = channel_instance
800
801
 
801
802
  # and watch for changes in the addresses
802
803
 
@@ -805,7 +806,7 @@ class ServiceManager:
805
806
  # create proxy
806
807
 
807
808
  proxy = DynamicProxy.create(service_type, channel_instance)
808
- self.proxy_cache[key] = proxy
809
+ self.proxy_cache[proxy_key] = proxy
809
810
 
810
811
  return proxy
811
812
 
File without changes
@@ -54,8 +54,13 @@ class Data:
54
54
  b: bool
55
55
  s: str
56
56
 
57
+ class PydanticAndData(BaseModel):
57
58
  p: Pydantic
58
59
 
60
+ @dataclass
61
+ class DataAndPydantic:
62
+ d: Data
63
+
59
64
  # service
60
65
 
61
66
  @service(name="test-service", description="cool")
@@ -6,7 +6,7 @@ import unittest
6
6
  from .common import TestAsyncService, TestAsyncRestService, Pydantic, Data, service_manager
7
7
 
8
8
  pydantic = Pydantic(i=1, f=1.0, b=True, s="s")
9
- data = Data(i=1, f=1.0, b=True, s="s", p=pydantic)
9
+ data = Data(i=1, f=1.0, b=True, s="s")
10
10
 
11
11
  class TestAsyncRemoteService(unittest.IsolatedAsyncioTestCase):
12
12
  @classmethod
@@ -25,7 +25,7 @@ class TestAsyncRemoteService(unittest.IsolatedAsyncioTestCase):
25
25
  result_pydantic = await test_service.pydantic(pydantic)
26
26
  self.assertEqual(result_pydantic, pydantic)
27
27
 
28
- async def test_dispatch_msgpack(self):
28
+ async def xtest_dispatch_msgpack(self):
29
29
  test_service = self.service_manager.get_service(TestAsyncService, preferred_channel="dispatch-msgpack")
30
30
 
31
31
  result = await test_service.hello("hello")
@@ -37,7 +37,7 @@ class TestAsyncRemoteService(unittest.IsolatedAsyncioTestCase):
37
37
  result_pydantic = await test_service.pydantic(pydantic)
38
38
  self.assertEqual(result_pydantic, pydantic)
39
39
 
40
- async def xtest_dispatch_rest(self):
40
+ async def xtest_rest(self):
41
41
  test_service = self.service_manager.get_service(TestAsyncRestService, preferred_channel="rest")
42
42
 
43
43
  result = await test_service.get("hello")
@@ -49,10 +49,8 @@ class TestAsyncRemoteService(unittest.IsolatedAsyncioTestCase):
49
49
  result = await test_service.delete("hello")
50
50
  self.assertEqual(result, "hello")
51
51
 
52
- #
53
-
54
- result_pydantic = test_service.post_pydantic("message", pydantic)
52
+ result_pydantic = await test_service.post_pydantic("message", pydantic)
55
53
  self.assertEqual(result_pydantic, pydantic)
56
54
 
57
- #result_data = test_service.post_data("message", data)
58
- #self.assertEqual(result_data, data)
55
+ result_data = await test_service.post_data("message", data)
56
+ self.assertEqual(result_data, data)
@@ -0,0 +1,69 @@
1
+ import unittest
2
+ from dataclasses import dataclass
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from aspyx_service.serialization import get_deserializer, get_serializer
7
+
8
+ class Pydantic(BaseModel):
9
+ i : int
10
+ f : float
11
+ b: bool
12
+ s: str
13
+
14
+ @dataclass
15
+ class Data:
16
+ i: int
17
+ f: float
18
+ b: bool
19
+ s: str
20
+
21
+ class PydanticAndData(BaseModel):
22
+ data: Data
23
+
24
+ @dataclass
25
+ class DataAndPydantic:
26
+ pydantic: Pydantic
27
+
28
+ pydantic = Pydantic(i=1, f=1.0, b=True, s="s")
29
+ data = Data(i=1, f=1.0, b=True, s="s")
30
+
31
+ p_plus_d = PydanticAndData(data=data)
32
+ d_plus_p = DataAndPydantic(pydantic=pydantic)
33
+
34
+ class TestSerialization(unittest.TestCase):
35
+ def test_pydantic(self):
36
+ serializer = get_serializer(Pydantic)
37
+ deserializer = get_deserializer(Pydantic)
38
+
39
+ output = serializer(pydantic)
40
+ reverse = deserializer(output)
41
+
42
+ self.assertEqual(reverse, pydantic)
43
+
44
+ def test_data(self):
45
+ serializer = get_serializer(Data)
46
+ deserializer = get_deserializer(Data)
47
+
48
+ output = serializer(data)
49
+ reverse = deserializer(output)
50
+
51
+ self.assertEqual(reverse, data)
52
+
53
+ def test_pydantic_plus_data(self):
54
+ serializer = get_serializer(PydanticAndData)
55
+ deserializer = get_deserializer(PydanticAndData)
56
+
57
+ output = serializer(p_plus_d)
58
+ reverse = deserializer(output)
59
+
60
+ self.assertEqual(p_plus_d, p_plus_d)
61
+
62
+ def test_data_plus_pydantic(self):
63
+ serializer = get_serializer(DataAndPydantic)
64
+ deserializer = get_deserializer(DataAndPydantic)
65
+
66
+ output = serializer(d_plus_p)
67
+ reverse = deserializer(output)
68
+
69
+ self.assertEqual(reverse, d_plus_p)
@@ -7,7 +7,7 @@ import unittest
7
7
  from .common import TestService, TestRestService, Test, Pydantic, Data, service_manager
8
8
 
9
9
  pydantic = Pydantic(i=1, f=1.0, b=True, s="s")
10
- data = Data(i=1, f=1.0, b=True, s="s", p=pydantic)
10
+ data = Data(i=1, f=1.0, b=True, s="s")
11
11
 
12
12
 
13
13
  class TestLocalService(unittest.TestCase):
@@ -37,7 +37,7 @@ class TestSyncRemoteService(unittest.TestCase):
37
37
  def setUpClass(cls):
38
38
  cls.service_manager = service_manager()
39
39
 
40
- def xtest_dispatch_json(self):
40
+ def test_dispatch_json(self):
41
41
  test_service = self.service_manager.get_service(TestService, preferred_channel="dispatch-json")
42
42
 
43
43
  result = test_service.hello("hello")
@@ -49,7 +49,7 @@ class TestSyncRemoteService(unittest.TestCase):
49
49
  result_pydantic = test_service.pydantic(pydantic)
50
50
  self.assertEqual(result_pydantic, pydantic)
51
51
 
52
- def xtest_dispatch_msgpack(self):
52
+ def test_dispatch_msgpack(self):
53
53
  test_service = self.service_manager.get_service(TestService, preferred_channel="dispatch-msgpack")
54
54
 
55
55
  result = test_service.hello("hello")
@@ -78,5 +78,5 @@ class TestSyncRemoteService(unittest.TestCase):
78
78
  result_pydantic = test_service.post_pydantic("message", pydantic)
79
79
  self.assertEqual(result_pydantic, pydantic)
80
80
 
81
- #result_data= test_service.post_data("message", data)
82
- #self.assertEqual(result_data, data)
81
+ result_data= test_service.post_data("message", data)
82
+ self.assertEqual(result_data, data)
@@ -1 +0,0 @@
1
-
File without changes