aspyx-service 0.10.2__tar.gz → 0.10.4__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 (36) hide show
  1. {aspyx_service-0.10.2 → aspyx_service-0.10.4}/PKG-INFO +41 -10
  2. {aspyx_service-0.10.2 → aspyx_service-0.10.4}/README.md +38 -8
  3. aspyx_service-0.10.4/performance-test/__init__.py +0 -0
  4. aspyx_service-0.10.4/performance-test/client.py +154 -0
  5. aspyx_service-0.10.4/performance-test/main.py +23 -0
  6. aspyx_service-0.10.4/performance-test/performance-test.py +279 -0
  7. aspyx_service-0.10.4/performance-test/readme.txt +1 -0
  8. aspyx_service-0.10.4/performance-test/server.py +154 -0
  9. aspyx_service-0.10.4/performance-test/start_server_8000.sh +3 -0
  10. aspyx_service-0.10.4/performance-test/start_server_8001.sh +3 -0
  11. {aspyx_service-0.10.2 → aspyx_service-0.10.4}/pyproject.toml +13 -2
  12. {aspyx_service-0.10.2 → aspyx_service-0.10.4}/src/aspyx_service/__init__.py +28 -4
  13. aspyx_service-0.10.4/src/aspyx_service/authorization.py +135 -0
  14. aspyx_service-0.10.4/src/aspyx_service/channels.py +471 -0
  15. {aspyx_service-0.10.2 → aspyx_service-0.10.4}/src/aspyx_service/healthcheck.py +1 -1
  16. {aspyx_service-0.10.2 → aspyx_service-0.10.4}/src/aspyx_service/registries.py +5 -5
  17. {aspyx_service-0.10.2 → aspyx_service-0.10.4}/src/aspyx_service/restchannel.py +17 -21
  18. {aspyx_service-0.10.2 → aspyx_service-0.10.4}/src/aspyx_service/serialization.py +6 -3
  19. {aspyx_service-0.10.2 → aspyx_service-0.10.4}/src/aspyx_service/server.py +139 -69
  20. {aspyx_service-0.10.2 → aspyx_service-0.10.4}/src/aspyx_service/service.py +55 -19
  21. aspyx_service-0.10.4/src/aspyx_service/session.py +97 -0
  22. aspyx_service-0.10.4/tests/__init__.py +0 -0
  23. {aspyx_service-0.10.2 → aspyx_service-0.10.4}/tests/common.py +144 -31
  24. aspyx_service-0.10.4/tests/config.yaml +16 -0
  25. aspyx_service-0.10.4/tests/test_async_service.py +53 -0
  26. aspyx_service-0.10.4/tests/test_healthcheck.py +51 -0
  27. aspyx_service-0.10.4/tests/test_jwt.py +403 -0
  28. aspyx_service-0.10.4/tests/test_serialization.py +71 -0
  29. aspyx_service-0.10.4/tests/test_service.py +80 -0
  30. aspyx_service-0.10.2/src/aspyx_service/channels.py +0 -277
  31. aspyx_service-0.10.2/tests/__init__.py +0 -1
  32. aspyx_service-0.10.2/tests/test_async_service.py +0 -58
  33. aspyx_service-0.10.2/tests/test_service.py +0 -82
  34. {aspyx_service-0.10.2 → aspyx_service-0.10.4}/.gitignore +0 -0
  35. {aspyx_service-0.10.2 → aspyx_service-0.10.4}/LICENSE +0 -0
  36. {aspyx_service-0.10.2/tests → aspyx_service-0.10.4/performance-test}/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.4
4
4
  Summary: Aspyx Service framework
5
5
  Author-email: Andreas Ernst <andreas.ernst7@gmail.com>
6
6
  License: MIT License
@@ -26,7 +26,8 @@ License: MIT License
26
26
  SOFTWARE.
27
27
  License-File: LICENSE
28
28
  Requires-Python: >=3.9
29
- Requires-Dist: aspyx>=1.5.1
29
+ Requires-Dist: aspyx>=1.5.3
30
+ Requires-Dist: cachetools~=5.5.2
30
31
  Requires-Dist: fastapi~=0.115.13
31
32
  Requires-Dist: httpx~=0.28.1
32
33
  Requires-Dist: msgpack~=1.1.1
@@ -60,6 +61,8 @@ Description-Content-Type: text/markdown
60
61
  - [Rest Calls](#rest-calls)
61
62
  - [Intercepting calls](#intercepting-calls)
62
63
  - [FastAPI server](#fastapi-server)
64
+ - [Session](#session)
65
+ - [Authorization](#authorization)
63
66
  - [Implementing Channels](#implementing-channels)
64
67
  - [Version History](#version-history)
65
68
 
@@ -70,19 +73,23 @@ that lets you deploy, discover and call services with different remoting protoco
70
73
 
71
74
  The basic design consists of four different concepts:
72
75
 
73
- !!! info "Service"
76
+ **Service**
77
+
74
78
  defines a group of methods that can be called either locally or remotely.
75
79
  These methods represent the functional interface exposed to clients — similar to an interface in traditional programming
76
80
 
77
- !!! info "Component"
81
+ **Component**
82
+
78
83
  a component bundles one or more services and declares the channels (protocols) used to expose them.
79
84
  Think of a component as a deployment unit or module.
80
85
 
81
- !!! info "Component Registry "
86
+ **Component Registry**
87
+
82
88
  acts as the central directory for managing available components.
83
89
  It allows the framework to register, discover, and resolve components and their services.
84
90
 
85
- !!! info "Channel"
91
+ **Channel**
92
+
86
93
  is a pluggable transport layer that defines how service method invocations are transmitted and handled.
87
94
 
88
95
  Let's look at the "interface" layer first.
@@ -111,7 +118,7 @@ class Module:
111
118
 
112
119
  @create()
113
120
  def create_registry(self) -> ConsulComponentRegistry:
114
- return ConsulComponentRegistry(Server.port, "http://localhost:8500") # a consul based registry!
121
+ return ConsulComponentRegistry(Server.port, Consul(host="localhost", port=8500)) # a consul based registry!
115
122
 
116
123
  environment = Environment(Module)
117
124
  service_manager = environment.get(ServiceManager)
@@ -162,10 +169,24 @@ environment = server.boot(Module)
162
169
  Of course, service can also be called locally. In case of multiple possible channels, a keyword argument is used to
163
170
  determine a specific channel. As a local channel has the name "local", the appropriate call is:
164
171
 
172
+ **Example**:
173
+
165
174
  ```python
166
175
  service = service_manager.get_service(TestService, preferred_channel="local")
167
176
  ```
168
177
 
178
+ The default can be set globally with the method `set_preferred_channel(channel: str)`
179
+
180
+ Injecting services is also possible via the decorator `@inject_service(preferred_channel=""")`
181
+
182
+ **Example**:
183
+
184
+ ```python
185
+ @inject_service()
186
+ def set_service(self, service: TestService)
187
+ self.service = service
188
+ ```
189
+
169
190
  ## Features
170
191
 
171
192
  The library offers:
@@ -348,12 +369,14 @@ Channels implement the possible transport layer protocols. In the sense of a dyn
348
369
  Several channels are implemented:
349
370
 
350
371
  - `dispatch-json`
351
- channel that dispatches generic `Request` objects via a `invoke` POST-call
372
+ channel that posts generic `Request` objects via a `invoke` POST-call
352
373
  - `dispatch-msgpack`
353
- channel that dispatches generic `Request` objects via a `invoke` POST-call after packing the json with msgpack
374
+ channel that posts generic `Request` objects via a `invoke` POST-call after packing the json with msgpack
354
375
  - `rest`
355
376
  channel that executes regular rest-calls as defined by a couple of decorators.
356
377
 
378
+ The `dispatch`channels have the big advantage, that you don`t have to deal with additional http decorators!
379
+
357
380
  All channels react on changed URLs as provided by the component registry.
358
381
 
359
382
  A so called `URLSelector` is used internally to provide URLs for every single call. Two subclasses exist that offer a different logic
@@ -361,7 +384,7 @@ A so called `URLSelector` is used internally to provide URLs for every single ca
361
384
  - `FirstURLSelector` always returns the first URL of the list of possible URLs
362
385
  - `RoundRobinURLSelector` switches sequentially between all URLs.
363
386
 
364
- To customize the behavior, an around advice can be implemented easily:
387
+ To customize the behavior, an `around` advice can be implemented easily:
365
388
 
366
389
  **Example**:
367
390
 
@@ -478,6 +501,14 @@ class Module():
478
501
  This setup will also expose all service interfaces decorated with the corresponding http decorators!
479
502
  No need to add any FastAPI decorators, since the mapping is already done internally!
480
503
 
504
+ ## Session
505
+
506
+ TODO
507
+
508
+ ## Authorization
509
+
510
+ TODO
511
+
481
512
  ## Implementing Channels
482
513
 
483
514
  To implement a new channel, you only need to derive from one of the possible base classes ( `Channel` or `HTTPXChannel` that already has a `httpx` client)
@@ -24,6 +24,8 @@
24
24
  - [Rest Calls](#rest-calls)
25
25
  - [Intercepting calls](#intercepting-calls)
26
26
  - [FastAPI server](#fastapi-server)
27
+ - [Session](#session)
28
+ - [Authorization](#authorization)
27
29
  - [Implementing Channels](#implementing-channels)
28
30
  - [Version History](#version-history)
29
31
 
@@ -34,19 +36,23 @@ that lets you deploy, discover and call services with different remoting protoco
34
36
 
35
37
  The basic design consists of four different concepts:
36
38
 
37
- !!! info "Service"
39
+ **Service**
40
+
38
41
  defines a group of methods that can be called either locally or remotely.
39
42
  These methods represent the functional interface exposed to clients — similar to an interface in traditional programming
40
43
 
41
- !!! info "Component"
44
+ **Component**
45
+
42
46
  a component bundles one or more services and declares the channels (protocols) used to expose them.
43
47
  Think of a component as a deployment unit or module.
44
48
 
45
- !!! info "Component Registry "
49
+ **Component Registry**
50
+
46
51
  acts as the central directory for managing available components.
47
52
  It allows the framework to register, discover, and resolve components and their services.
48
53
 
49
- !!! info "Channel"
54
+ **Channel**
55
+
50
56
  is a pluggable transport layer that defines how service method invocations are transmitted and handled.
51
57
 
52
58
  Let's look at the "interface" layer first.
@@ -75,7 +81,7 @@ class Module:
75
81
 
76
82
  @create()
77
83
  def create_registry(self) -> ConsulComponentRegistry:
78
- return ConsulComponentRegistry(Server.port, "http://localhost:8500") # a consul based registry!
84
+ return ConsulComponentRegistry(Server.port, Consul(host="localhost", port=8500)) # a consul based registry!
79
85
 
80
86
  environment = Environment(Module)
81
87
  service_manager = environment.get(ServiceManager)
@@ -126,10 +132,24 @@ environment = server.boot(Module)
126
132
  Of course, service can also be called locally. In case of multiple possible channels, a keyword argument is used to
127
133
  determine a specific channel. As a local channel has the name "local", the appropriate call is:
128
134
 
135
+ **Example**:
136
+
129
137
  ```python
130
138
  service = service_manager.get_service(TestService, preferred_channel="local")
131
139
  ```
132
140
 
141
+ The default can be set globally with the method `set_preferred_channel(channel: str)`
142
+
143
+ Injecting services is also possible via the decorator `@inject_service(preferred_channel=""")`
144
+
145
+ **Example**:
146
+
147
+ ```python
148
+ @inject_service()
149
+ def set_service(self, service: TestService)
150
+ self.service = service
151
+ ```
152
+
133
153
  ## Features
134
154
 
135
155
  The library offers:
@@ -312,12 +332,14 @@ Channels implement the possible transport layer protocols. In the sense of a dyn
312
332
  Several channels are implemented:
313
333
 
314
334
  - `dispatch-json`
315
- channel that dispatches generic `Request` objects via a `invoke` POST-call
335
+ channel that posts generic `Request` objects via a `invoke` POST-call
316
336
  - `dispatch-msgpack`
317
- channel that dispatches generic `Request` objects via a `invoke` POST-call after packing the json with msgpack
337
+ channel that posts generic `Request` objects via a `invoke` POST-call after packing the json with msgpack
318
338
  - `rest`
319
339
  channel that executes regular rest-calls as defined by a couple of decorators.
320
340
 
341
+ The `dispatch`channels have the big advantage, that you don`t have to deal with additional http decorators!
342
+
321
343
  All channels react on changed URLs as provided by the component registry.
322
344
 
323
345
  A so called `URLSelector` is used internally to provide URLs for every single call. Two subclasses exist that offer a different logic
@@ -325,7 +347,7 @@ A so called `URLSelector` is used internally to provide URLs for every single ca
325
347
  - `FirstURLSelector` always returns the first URL of the list of possible URLs
326
348
  - `RoundRobinURLSelector` switches sequentially between all URLs.
327
349
 
328
- To customize the behavior, an around advice can be implemented easily:
350
+ To customize the behavior, an `around` advice can be implemented easily:
329
351
 
330
352
  **Example**:
331
353
 
@@ -442,6 +464,14 @@ class Module():
442
464
  This setup will also expose all service interfaces decorated with the corresponding http decorators!
443
465
  No need to add any FastAPI decorators, since the mapping is already done internally!
444
466
 
467
+ ## Session
468
+
469
+ TODO
470
+
471
+ ## Authorization
472
+
473
+ TODO
474
+
445
475
  ## Implementing Channels
446
476
 
447
477
  To implement a new channel, you only need to derive from one of the possible base classes ( `Channel` or `HTTPXChannel` that already has a `httpx` client)
File without changes
@@ -0,0 +1,154 @@
1
+ """
2
+ Tests
3
+ """
4
+
5
+ from abc import abstractmethod
6
+ from dataclasses import dataclass
7
+
8
+ from pydantic import BaseModel
9
+
10
+ from aspyx.di import module
11
+
12
+ from aspyx_service import ServiceModule, delete, post, put, get, rest, Body
13
+ from aspyx_service.service import component, Component, Service, service
14
+
15
+ class Pydantic(BaseModel):
16
+ i: int
17
+ f: float
18
+ b: bool
19
+ s: str
20
+
21
+ str0 : str
22
+ str1: str
23
+ str2: str
24
+ str3: str
25
+ str4: str
26
+ str5: str
27
+ str6: str
28
+ str7: str
29
+ str8: str
30
+ str9: str
31
+
32
+
33
+ @dataclass
34
+ class Data:
35
+ i: int
36
+ f: float
37
+ b: bool
38
+ s: str
39
+
40
+ str0: str
41
+ str1: str
42
+ str2: str
43
+ str3: str
44
+ str4: str
45
+ str5: str
46
+ str6: str
47
+ str7: str
48
+ str8: str
49
+ str9: str
50
+
51
+ class PydanticAndData(BaseModel):
52
+ p: Pydantic
53
+
54
+ @dataclass
55
+ class DataAndPydantic:
56
+ d: Data
57
+
58
+ # service
59
+
60
+ @service(name="test-service", description="cool")
61
+ class TestService(Service):
62
+ @abstractmethod
63
+ def hello(self, message: str) -> str:
64
+ pass
65
+
66
+ @abstractmethod
67
+ def throw(self, message: str) -> str:
68
+ pass
69
+
70
+ @abstractmethod
71
+ def data(self, data: Data) -> Data:
72
+ pass
73
+
74
+ @abstractmethod
75
+ def pydantic(self, data: Pydantic) -> Pydantic:
76
+ pass
77
+
78
+ @service(name="test-async-service", description="cool")
79
+ class TestAsyncService(Service):
80
+ @abstractmethod
81
+ async def hello(self, message: str) -> str:
82
+ pass
83
+
84
+ @abstractmethod
85
+ async def data(self, data: Data) -> Data:
86
+ pass
87
+
88
+ @abstractmethod
89
+ async def pydantic(self, data: Pydantic) -> Pydantic:
90
+ pass
91
+
92
+ @service(name="test-rest-service", description="cool")
93
+ @rest("/api")
94
+ class TestRestService(Service):
95
+ @abstractmethod
96
+ @get("/hello/{message}")
97
+ def get(self, message: str) -> str:
98
+ pass
99
+
100
+ @put("/hello/{message}")
101
+ def put(self, message: str) -> str:
102
+ pass
103
+
104
+ @post("/hello/{message}")
105
+ def post_pydantic(self, message: str, data: Body(Pydantic)) -> Pydantic:
106
+ pass
107
+
108
+ @post("/hello/{message}")
109
+ def post_data(self, message: str, data: Body(Data)) -> Data:
110
+ pass
111
+
112
+ @delete("/hello/{message}")
113
+ def delete(self, message: str) -> str:
114
+ pass
115
+
116
+ @service(name="test-async-rest-service", description="cool")
117
+ @rest("/async-api")
118
+ class TestAsyncRestService(Service):
119
+ @abstractmethod
120
+ @get("/hello/{message}")
121
+ async def get(self, message: str) -> str:
122
+ pass
123
+
124
+ @put("/hello/{message}")
125
+ async def put(self, message: str) -> str:
126
+ pass
127
+
128
+ @post("/hello/{message}")
129
+ async def post_pydantic(self, message: str, data: Body(Pydantic)) -> Pydantic:
130
+ pass
131
+
132
+ @post("/hello/{message}")
133
+ async def post_data(self, message: str, data: Body(Data)) -> Data:
134
+ pass
135
+
136
+ @delete("/hello/{message}")
137
+ async def delete(self, message: str) -> str:
138
+ pass
139
+
140
+ @component(services =[
141
+ TestService,
142
+ TestAsyncService,
143
+ TestRestService,
144
+ TestAsyncRestService
145
+ ])
146
+ class TestComponent(Component): # pylint: disable=abstract-method
147
+ pass
148
+
149
+ # module
150
+
151
+ @module(imports=[ServiceModule])
152
+ class ClientModule:
153
+ def __init__(self):
154
+ pass
@@ -0,0 +1,23 @@
1
+ import logging
2
+ import os
3
+
4
+ from aspyx.util import Logger
5
+ from aspyx_service import FastAPIServer
6
+ from server import ServerModule
7
+
8
+ Logger.configure(default_level=logging.DEBUG, levels={
9
+ "httpx": logging.ERROR,
10
+ "aspyx.di": logging.ERROR,
11
+ "aspyx.di.aop": logging.ERROR,
12
+ "aspyx.service": logging.ERROR
13
+ })
14
+
15
+ PORT = int(os.getenv("FAST_API_PORT", 8000))
16
+
17
+ FastAPIServer.boot(module=ServerModule, host="0.0.0.0", port=PORT, start = False)
18
+
19
+ app = FastAPIServer.fast_api
20
+
21
+ if __name__ == "__main__":
22
+ import uvicorn
23
+ uvicorn.run("main:app", host="0.0.0.0", port=PORT, reload=True, log_level="warning", access_log=False)
@@ -0,0 +1,279 @@
1
+ """
2
+ Tests
3
+ """
4
+ import asyncio
5
+ import threading
6
+ import time
7
+ import logging
8
+
9
+ from typing import Callable, TypeVar, Type, Awaitable, Any, Dict, cast
10
+
11
+ from consul import Consul
12
+
13
+ from aspyx.di import module, Environment, create
14
+ from aspyx.di.aop import advice, around, methods, Invocation
15
+ from aspyx.util import Logger
16
+
17
+ from aspyx_service import ConsulComponentRegistry
18
+ from aspyx_service.service import ServiceManager, ComponentRegistry, Channel
19
+
20
+ Logger.configure(default_level=logging.DEBUG, levels={
21
+ "httpx": logging.ERROR,
22
+ "aspyx.di": logging.ERROR,
23
+ "aspyx.di.aop": logging.ERROR,
24
+ "aspyx.service": logging.ERROR
25
+ })
26
+
27
+ from client import TestService, TestRestService, Pydantic, Data, TestAsyncRestService, TestAsyncService, ClientModule
28
+
29
+ # main
30
+
31
+ @advice
32
+ class ChannelAdvice:
33
+ def __init__(self):
34
+ pass
35
+
36
+ @around(methods().named("customize").of_type(Channel))
37
+ def customize_channel(self, invocation: Invocation):
38
+ channel = cast(Channel, invocation.args[0])
39
+
40
+ channel.select_round_robin() # or select_first_url()
41
+
42
+ return invocation.proceed()
43
+
44
+ @module(imports=[ClientModule])
45
+ class TestModule:
46
+ def __init__(self):
47
+ pass
48
+
49
+ @create()
50
+ def create_registry(self) -> ComponentRegistry:
51
+ return ConsulComponentRegistry(port=8000, consul=Consul(host="localhost", port=8500))
52
+
53
+ def boot() -> ServiceManager:
54
+ environment = Environment(TestModule)
55
+
56
+ service_manager = environment.get(ServiceManager)
57
+
58
+ return service_manager
59
+
60
+ T = TypeVar("T")
61
+
62
+ # main
63
+
64
+ lorem_ipsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
65
+
66
+ pydantic = Pydantic(i=1, f=1.0, b=True, s="s",
67
+ str0=lorem_ipsum,
68
+ str1=lorem_ipsum,
69
+ str2=lorem_ipsum,
70
+ str3=lorem_ipsum,
71
+ str4=lorem_ipsum,
72
+ str5=lorem_ipsum,
73
+ str6=lorem_ipsum,
74
+ str7=lorem_ipsum,
75
+ str8=lorem_ipsum,
76
+ str9=lorem_ipsum
77
+ )
78
+ data = Data(i=1, f=1.0, b=True, s="s",
79
+ str0=lorem_ipsum,
80
+ str1=lorem_ipsum,
81
+ str2=lorem_ipsum,
82
+ str3=lorem_ipsum,
83
+ str4=lorem_ipsum,
84
+ str5=lorem_ipsum,
85
+ str6=lorem_ipsum,
86
+ str7=lorem_ipsum,
87
+ str8=lorem_ipsum,
88
+ str9=lorem_ipsum
89
+ )
90
+
91
+ def run_loops(name: str, loops: int, type: Type[T], instance: T, callable: Callable[[T], None]):
92
+ start = time.perf_counter()
93
+ for _ in range(loops):
94
+ callable(instance)
95
+
96
+ end = time.perf_counter()
97
+ avg_ms = ((end - start) / loops) * 1000
98
+
99
+ print(f"run {name}, loops={loops}: avg={avg_ms:.3f} ms")
100
+
101
+ async def run_async_loops(name: str, loops: int, type: Type[T], instance: T, callable: Callable[[T], Awaitable[Any]]):
102
+ start = time.perf_counter()
103
+ for _ in range(loops):
104
+ await callable(instance)
105
+
106
+ end = time.perf_counter()
107
+ avg_ms = ((end - start) / loops) * 1000
108
+
109
+ print(f"run {name}, loops={loops}: avg={avg_ms:.3f} ms")
110
+
111
+ def run_threaded_async_loops(name: str, loops: int, n_threads: int, type: Type[T], instance: T, callable: Callable[[T], Awaitable[Any]]):
112
+ threads = []
113
+
114
+ def worker(thread_id: int):
115
+ #print(f"worker {thread_id} running on thread {threading.current_thread().name}")
116
+
117
+ loop = asyncio.new_event_loop()
118
+ asyncio.set_event_loop(loop)
119
+
120
+ async def run():
121
+ for i in range(loops):
122
+ await callable(instance)
123
+
124
+ loop.run_until_complete(run())
125
+ loop.close()
126
+
127
+ start = time.perf_counter()
128
+
129
+ for t_id in range(0, n_threads):
130
+ thread = threading.Thread(target=worker, args=(t_id,))
131
+ threads.append(thread)
132
+ thread.start()
133
+
134
+ for thread in threads:
135
+ thread.join()
136
+
137
+ end = time.perf_counter()
138
+ took = (end - start) * 1000
139
+ avg_ms = ((end - start) / (n_threads * loops)) * 1000
140
+
141
+ print(f"{name} {loops} in {n_threads} threads: {took} ms, avg: {avg_ms}ms")
142
+
143
+ def run_threaded_sync_loops(name: str, loops: int, n_threads: int, type: Type[T], instance: T, callable: Callable[[T], Any]):
144
+ threads = []
145
+
146
+ def worker(thread_id: int):
147
+ #print(f"worker {thread_id} running on thread {threading.current_thread().name}")
148
+
149
+ loop = asyncio.new_event_loop()
150
+ asyncio.set_event_loop(loop)
151
+
152
+ async def run():
153
+ for i in range(loops):
154
+ callable(instance)
155
+
156
+ loop.run_until_complete(run())
157
+ loop.close()
158
+
159
+ start = time.perf_counter()
160
+
161
+ for t_id in range(0, n_threads):
162
+ thread = threading.Thread(target=worker, args=(t_id,))
163
+ threads.append(thread)
164
+ thread.start()
165
+
166
+ for thread in threads:
167
+ thread.join()
168
+
169
+ end = time.perf_counter()
170
+ took = (end - start) * 1000
171
+ avg_ms = ((end - start) / (n_threads * loops)) * 1000
172
+
173
+ print(f"{name} {loops} in {n_threads} threads: {took} ms, avg: {avg_ms}ms")
174
+
175
+ manager = boot()
176
+
177
+ async def main():
178
+ print("start tests...")
179
+
180
+ # get service manager
181
+
182
+ loops = 1000
183
+
184
+ # tests
185
+
186
+ run_loops("rest", loops, TestRestService, manager.get_service(TestRestService, preferred_channel="rest"), lambda service: service.get("world"))
187
+ run_loops("json", loops, TestService, manager.get_service(TestService, preferred_channel="dispatch-json"), lambda service: service.hello("world"))
188
+ run_loops("msgpack", loops, TestService, manager.get_service(TestService, preferred_channel="dispatch-msgpack"), lambda service: service.hello("world"))
189
+
190
+ # pydantic
191
+
192
+ run_loops("rest & pydantic", loops, TestRestService, manager.get_service(TestRestService, preferred_channel="rest"), lambda service: service.post_pydantic("hello", pydantic))
193
+ run_loops("json & pydantic", loops, TestService, manager.get_service(TestService, preferred_channel="dispatch-json"), lambda service: service.pydantic(pydantic))
194
+ run_loops("msgpack & pydantic", loops, TestService, manager.get_service(TestService, preferred_channel="dispatch-msgpack"), lambda service: service.pydantic(pydantic))
195
+
196
+ # data class
197
+
198
+ run_loops("rest & data", loops, TestRestService, manager.get_service(TestRestService, preferred_channel="rest"),
199
+ lambda service: service.post_data("hello", data))
200
+ run_loops("json & data", loops, TestService,
201
+ manager.get_service(TestService, preferred_channel="dispatch-json"),
202
+ lambda service: service.data(data))
203
+ run_loops("msgpack & data", loops, TestService,
204
+ manager.get_service(TestService, preferred_channel="dispatch-msgpack"),
205
+ lambda service: service.data(data))
206
+
207
+ # async
208
+
209
+ await run_async_loops("async rest", loops, TestAsyncRestService, manager.get_service(TestAsyncRestService, preferred_channel="rest"),
210
+ lambda service: service.get("world"))
211
+ await run_async_loops("async json", loops, TestAsyncService, manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
212
+ lambda service: service.hello("world"))
213
+ await run_async_loops("async msgpack", loops, TestAsyncService, manager.get_service(TestAsyncService, preferred_channel="dispatch-msgpack"),
214
+ lambda service: service.hello("world"))
215
+
216
+ # pydantic
217
+
218
+ await run_async_loops("async rest & pydantic", loops, TestAsyncRestService, manager.get_service(TestAsyncRestService, preferred_channel="rest"),
219
+ lambda service: service.post_pydantic("hello", pydantic))
220
+ await run_async_loops("async json & pydantic", loops, TestAsyncService,
221
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
222
+ lambda service: service.pydantic(pydantic))
223
+ await run_async_loops("async msgpack & pydantic", loops, TestAsyncService,
224
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-msgpack"),
225
+ lambda service: service.pydantic(pydantic))
226
+
227
+ # data class
228
+
229
+ # pydantic
230
+
231
+ await run_async_loops("async rest & data", loops, TestAsyncRestService, manager.get_service(TestAsyncRestService, preferred_channel="rest"),
232
+ lambda service: service.post_data("hello", data))
233
+ await run_async_loops("async json & data", loops, TestAsyncService,
234
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
235
+ lambda service: service.data(data))
236
+ await run_async_loops("async msgpack & data", loops, TestAsyncService,
237
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-msgpack"),
238
+ lambda service: service.data(data))
239
+
240
+ # a real thread test
241
+
242
+ # sync
243
+
244
+ run_threaded_sync_loops("threaded sync json, 1 thread", loops, 1, TestService,
245
+ manager.get_service(TestService, preferred_channel="dispatch-json"),
246
+ lambda service: service.hello("world"))
247
+ run_threaded_sync_loops("threaded sync json, 2 thread", loops, 2, TestService,
248
+ manager.get_service(TestService, preferred_channel="dispatch-json"),
249
+ lambda service: service.hello("world"))
250
+ run_threaded_sync_loops("threaded sync json, 4 thread", loops, 4, TestService,
251
+ manager.get_service(TestService, preferred_channel="dispatch-json"),
252
+ lambda service: service.hello("world"))
253
+ run_threaded_sync_loops("threaded sync json, 8 thread", loops, 8, TestService,
254
+ manager.get_service(TestService, preferred_channel="dispatch-json"),
255
+ lambda service: service.hello("world"))
256
+ run_threaded_sync_loops("threaded sync json, 16 thread", loops, 16, TestService,
257
+ manager.get_service(TestService, preferred_channel="dispatch-json"),
258
+ lambda service: service.hello("world"))
259
+
260
+ # async
261
+
262
+ run_threaded_async_loops("threaded async json, 1 thread", loops, 1, TestAsyncService,
263
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
264
+ lambda service: service.hello("world") )
265
+ run_threaded_async_loops("threaded async json, 2 thread", loops, 2, TestAsyncService,
266
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
267
+ lambda service: service.hello("world"))
268
+ run_threaded_async_loops("threaded async json, 4 thread", loops, 4, TestAsyncService,
269
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
270
+ lambda service: service.hello("world"))
271
+ run_threaded_async_loops("threaded async json, 8 thread", loops, 8, TestAsyncService,
272
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
273
+ lambda service: service.hello("world"))
274
+ run_threaded_async_loops("threaded async json, 16 thread", loops, 16, TestAsyncService,
275
+ manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
276
+ lambda service: service.hello("world"))
277
+
278
+ if __name__ == "__main__":
279
+ asyncio.run(main())