aspyx-service 0.10.3__tar.gz → 0.10.5__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.
- {aspyx_service-0.10.3 → aspyx_service-0.10.5}/PKG-INFO +56 -12
- {aspyx_service-0.10.3 → aspyx_service-0.10.5}/README.md +54 -10
- aspyx_service-0.10.5/performance-test/client.py +155 -0
- aspyx_service-0.10.5/performance-test/main.py +34 -0
- {aspyx_service-0.10.3/performance_tests → aspyx_service-0.10.5/performance-test}/performance-test.py +92 -12
- aspyx_service-0.10.5/performance-test/readme.txt +1 -0
- aspyx_service-0.10.5/performance-test/server.py +168 -0
- aspyx_service-0.10.5/performance-test/start_server_8000.sh +3 -0
- aspyx_service-0.10.5/performance-test/start_server_8001.sh +3 -0
- {aspyx_service-0.10.3 → aspyx_service-0.10.5}/pyproject.toml +12 -2
- {aspyx_service-0.10.3 → aspyx_service-0.10.5}/src/aspyx_service/__init__.py +30 -5
- aspyx_service-0.10.5/src/aspyx_service/authorization.py +126 -0
- aspyx_service-0.10.5/src/aspyx_service/channels.py +473 -0
- {aspyx_service-0.10.3 → aspyx_service-0.10.5}/src/aspyx_service/healthcheck.py +1 -1
- {aspyx_service-0.10.3 → aspyx_service-0.10.5}/src/aspyx_service/registries.py +5 -5
- {aspyx_service-0.10.3 → aspyx_service-0.10.5}/src/aspyx_service/restchannel.py +13 -20
- aspyx_service-0.10.5/src/aspyx_service/server.py +360 -0
- {aspyx_service-0.10.3 → aspyx_service-0.10.5}/src/aspyx_service/service.py +47 -12
- aspyx_service-0.10.5/src/aspyx_service/session.py +136 -0
- aspyx_service-0.10.5/tests/__init__.py +0 -0
- {aspyx_service-0.10.3 → aspyx_service-0.10.5}/tests/common.py +176 -34
- aspyx_service-0.10.5/tests/config.yaml +16 -0
- aspyx_service-0.10.5/tests/test_async_service.py +58 -0
- aspyx_service-0.10.5/tests/test_healthcheck.py +51 -0
- aspyx_service-0.10.5/tests/test_jwt.py +422 -0
- {aspyx_service-0.10.3 → aspyx_service-0.10.5}/tests/test_serialization.py +9 -7
- aspyx_service-0.10.5/tests/test_service.py +80 -0
- aspyx_service-0.10.3/src/aspyx_service/channels.py +0 -282
- aspyx_service-0.10.3/src/aspyx_service/serialization.py +0 -137
- aspyx_service-0.10.3/src/aspyx_service/server.py +0 -228
- aspyx_service-0.10.3/tests/test_async_service.py +0 -56
- aspyx_service-0.10.3/tests/test_service.py +0 -82
- {aspyx_service-0.10.3 → aspyx_service-0.10.5}/.gitignore +0 -0
- {aspyx_service-0.10.3 → aspyx_service-0.10.5}/LICENSE +0 -0
- {aspyx_service-0.10.3/tests → aspyx_service-0.10.5/performance-test}/__init__.py +0 -0
- {aspyx_service-0.10.3/tests → aspyx_service-0.10.5/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.
|
|
3
|
+
Version: 0.10.5
|
|
4
4
|
Summary: Aspyx Service framework
|
|
5
5
|
Author-email: Andreas Ernst <andreas.ernst7@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -26,7 +26,7 @@ License: MIT License
|
|
|
26
26
|
SOFTWARE.
|
|
27
27
|
License-File: LICENSE
|
|
28
28
|
Requires-Python: >=3.9
|
|
29
|
-
Requires-Dist: aspyx>=1.
|
|
29
|
+
Requires-Dist: aspyx>=1.6.0
|
|
30
30
|
Requires-Dist: fastapi~=0.115.13
|
|
31
31
|
Requires-Dist: httpx~=0.28.1
|
|
32
32
|
Requires-Dist: msgpack~=1.1.1
|
|
@@ -60,6 +60,8 @@ Description-Content-Type: text/markdown
|
|
|
60
60
|
- [Rest Calls](#rest-calls)
|
|
61
61
|
- [Intercepting calls](#intercepting-calls)
|
|
62
62
|
- [FastAPI server](#fastapi-server)
|
|
63
|
+
- [Session](#session)
|
|
64
|
+
- [Authorization](#authorization)
|
|
63
65
|
- [Implementing Channels](#implementing-channels)
|
|
64
66
|
- [Version History](#version-history)
|
|
65
67
|
|
|
@@ -107,18 +109,20 @@ class TestComponent(Component):
|
|
|
107
109
|
After booting the DI infrastructure with a main module we could already call a service:
|
|
108
110
|
|
|
109
111
|
**Example**:
|
|
112
|
+
|
|
110
113
|
```python
|
|
111
114
|
@module(imports=[ServiceModule])
|
|
112
115
|
class Module:
|
|
113
116
|
def __init__(self):
|
|
114
117
|
pass
|
|
115
|
-
|
|
118
|
+
|
|
116
119
|
@create()
|
|
117
120
|
def create_registry(self) -> ConsulComponentRegistry:
|
|
118
|
-
return ConsulComponentRegistry(Server.port, Consul(host="localhost", port=8500))
|
|
121
|
+
return ConsulComponentRegistry(Server.port, Consul(host="localhost", port=8500)) # a consul based registry!
|
|
122
|
+
|
|
119
123
|
|
|
120
124
|
environment = Environment(Module)
|
|
121
|
-
service_manager = environment.
|
|
125
|
+
service_manager = environment.read(ServiceManager)
|
|
122
126
|
|
|
123
127
|
service = service_manager.get_service(TestService)
|
|
124
128
|
|
|
@@ -166,10 +170,24 @@ environment = server.boot(Module)
|
|
|
166
170
|
Of course, service can also be called locally. In case of multiple possible channels, a keyword argument is used to
|
|
167
171
|
determine a specific channel. As a local channel has the name "local", the appropriate call is:
|
|
168
172
|
|
|
173
|
+
**Example**:
|
|
174
|
+
|
|
169
175
|
```python
|
|
170
176
|
service = service_manager.get_service(TestService, preferred_channel="local")
|
|
171
177
|
```
|
|
172
178
|
|
|
179
|
+
The default can be set globally with the method `set_preferred_channel(channel: str)`
|
|
180
|
+
|
|
181
|
+
Injecting services is also possible via the decorator `@inject_service(preferred_channel=""")`
|
|
182
|
+
|
|
183
|
+
**Example**:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
@inject_service()
|
|
187
|
+
def set_service(self, service: TestService)
|
|
188
|
+
self.service = service
|
|
189
|
+
```
|
|
190
|
+
|
|
173
191
|
## Features
|
|
174
192
|
|
|
175
193
|
The library offers:
|
|
@@ -185,6 +203,14 @@ The library offers:
|
|
|
185
203
|
As well as the DI and AOP core, all mechanisms are heavily optimized.
|
|
186
204
|
A simple benchmark resulted in message roundtrips in significanlty under a ms per call.
|
|
187
205
|
|
|
206
|
+
## Installation
|
|
207
|
+
|
|
208
|
+
Just install from PyPI with
|
|
209
|
+
|
|
210
|
+
`pip install aspyx-service`
|
|
211
|
+
|
|
212
|
+
The library is tested with all Python version >= 3.9
|
|
213
|
+
|
|
188
214
|
Let's see some details
|
|
189
215
|
|
|
190
216
|
## Service and Component declaration
|
|
@@ -352,12 +378,14 @@ Channels implement the possible transport layer protocols. In the sense of a dyn
|
|
|
352
378
|
Several channels are implemented:
|
|
353
379
|
|
|
354
380
|
- `dispatch-json`
|
|
355
|
-
channel that
|
|
381
|
+
channel that posts generic `Request` objects via a `invoke` POST-call
|
|
356
382
|
- `dispatch-msgpack`
|
|
357
|
-
channel that
|
|
383
|
+
channel that posts generic `Request` objects via a `invoke` POST-call after packing the json with msgpack
|
|
358
384
|
- `rest`
|
|
359
385
|
channel that executes regular rest-calls as defined by a couple of decorators.
|
|
360
386
|
|
|
387
|
+
The `dispatch`channels have the big advantage, that you don`t have to deal with additional http decorators!
|
|
388
|
+
|
|
361
389
|
All channels react on changed URLs as provided by the component registry.
|
|
362
390
|
|
|
363
391
|
A so called `URLSelector` is used internally to provide URLs for every single call. Two subclasses exist that offer a different logic
|
|
@@ -465,23 +493,39 @@ class ChannelAdvice:
|
|
|
465
493
|
|
|
466
494
|
## FastAPI server
|
|
467
495
|
|
|
468
|
-
|
|
496
|
+
The required - `FastAPI` - infrastructure to expose those services requires:
|
|
469
497
|
|
|
498
|
+
- a `FastAPI` instance
|
|
499
|
+
- an injectable `FastAPIServer`
|
|
500
|
+
- and a final `boot` call with the root module, which will return an `Environment`
|
|
470
501
|
|
|
471
502
|
```python
|
|
472
|
-
|
|
473
|
-
|
|
503
|
+
fast_api = FastAPI() # so you can run it with uvivorn from command-line
|
|
504
|
+
|
|
505
|
+
@module(imports=[ServiceModule])
|
|
506
|
+
class Module:
|
|
474
507
|
def __init__(self):
|
|
475
508
|
pass
|
|
509
|
+
|
|
510
|
+
@create()
|
|
511
|
+
def create_server(self, service_manager: ServiceManager, component_registry: ComponentRegistry) -> FastAPIServer:
|
|
512
|
+
return FastAPIServer(fastapi, service_manager, component_registry)
|
|
476
513
|
|
|
477
|
-
server = FastAPIServer(host="0.0.0.0", port=8000)
|
|
478
514
|
|
|
479
|
-
|
|
515
|
+
environment = FastAPIServer.boot(Moudle, host="0.0.0.0", port=8000)
|
|
480
516
|
```
|
|
481
517
|
|
|
482
518
|
This setup will also expose all service interfaces decorated with the corresponding http decorators!
|
|
483
519
|
No need to add any FastAPI decorators, since the mapping is already done internally!
|
|
484
520
|
|
|
521
|
+
## Session
|
|
522
|
+
|
|
523
|
+
TODO
|
|
524
|
+
|
|
525
|
+
## Authorization
|
|
526
|
+
|
|
527
|
+
TODO
|
|
528
|
+
|
|
485
529
|
## Implementing Channels
|
|
486
530
|
|
|
487
531
|
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
|
|
|
@@ -71,18 +73,20 @@ class TestComponent(Component):
|
|
|
71
73
|
After booting the DI infrastructure with a main module we could already call a service:
|
|
72
74
|
|
|
73
75
|
**Example**:
|
|
76
|
+
|
|
74
77
|
```python
|
|
75
78
|
@module(imports=[ServiceModule])
|
|
76
79
|
class Module:
|
|
77
80
|
def __init__(self):
|
|
78
81
|
pass
|
|
79
|
-
|
|
82
|
+
|
|
80
83
|
@create()
|
|
81
84
|
def create_registry(self) -> ConsulComponentRegistry:
|
|
82
|
-
return ConsulComponentRegistry(Server.port, Consul(host="localhost", port=8500))
|
|
85
|
+
return ConsulComponentRegistry(Server.port, Consul(host="localhost", port=8500)) # a consul based registry!
|
|
86
|
+
|
|
83
87
|
|
|
84
88
|
environment = Environment(Module)
|
|
85
|
-
service_manager = environment.
|
|
89
|
+
service_manager = environment.read(ServiceManager)
|
|
86
90
|
|
|
87
91
|
service = service_manager.get_service(TestService)
|
|
88
92
|
|
|
@@ -130,10 +134,24 @@ environment = server.boot(Module)
|
|
|
130
134
|
Of course, service can also be called locally. In case of multiple possible channels, a keyword argument is used to
|
|
131
135
|
determine a specific channel. As a local channel has the name "local", the appropriate call is:
|
|
132
136
|
|
|
137
|
+
**Example**:
|
|
138
|
+
|
|
133
139
|
```python
|
|
134
140
|
service = service_manager.get_service(TestService, preferred_channel="local")
|
|
135
141
|
```
|
|
136
142
|
|
|
143
|
+
The default can be set globally with the method `set_preferred_channel(channel: str)`
|
|
144
|
+
|
|
145
|
+
Injecting services is also possible via the decorator `@inject_service(preferred_channel=""")`
|
|
146
|
+
|
|
147
|
+
**Example**:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
@inject_service()
|
|
151
|
+
def set_service(self, service: TestService)
|
|
152
|
+
self.service = service
|
|
153
|
+
```
|
|
154
|
+
|
|
137
155
|
## Features
|
|
138
156
|
|
|
139
157
|
The library offers:
|
|
@@ -149,6 +167,14 @@ The library offers:
|
|
|
149
167
|
As well as the DI and AOP core, all mechanisms are heavily optimized.
|
|
150
168
|
A simple benchmark resulted in message roundtrips in significanlty under a ms per call.
|
|
151
169
|
|
|
170
|
+
## Installation
|
|
171
|
+
|
|
172
|
+
Just install from PyPI with
|
|
173
|
+
|
|
174
|
+
`pip install aspyx-service`
|
|
175
|
+
|
|
176
|
+
The library is tested with all Python version >= 3.9
|
|
177
|
+
|
|
152
178
|
Let's see some details
|
|
153
179
|
|
|
154
180
|
## Service and Component declaration
|
|
@@ -316,12 +342,14 @@ Channels implement the possible transport layer protocols. In the sense of a dyn
|
|
|
316
342
|
Several channels are implemented:
|
|
317
343
|
|
|
318
344
|
- `dispatch-json`
|
|
319
|
-
channel that
|
|
345
|
+
channel that posts generic `Request` objects via a `invoke` POST-call
|
|
320
346
|
- `dispatch-msgpack`
|
|
321
|
-
channel that
|
|
347
|
+
channel that posts generic `Request` objects via a `invoke` POST-call after packing the json with msgpack
|
|
322
348
|
- `rest`
|
|
323
349
|
channel that executes regular rest-calls as defined by a couple of decorators.
|
|
324
350
|
|
|
351
|
+
The `dispatch`channels have the big advantage, that you don`t have to deal with additional http decorators!
|
|
352
|
+
|
|
325
353
|
All channels react on changed URLs as provided by the component registry.
|
|
326
354
|
|
|
327
355
|
A so called `URLSelector` is used internally to provide URLs for every single call. Two subclasses exist that offer a different logic
|
|
@@ -429,23 +457,39 @@ class ChannelAdvice:
|
|
|
429
457
|
|
|
430
458
|
## FastAPI server
|
|
431
459
|
|
|
432
|
-
|
|
460
|
+
The required - `FastAPI` - infrastructure to expose those services requires:
|
|
433
461
|
|
|
462
|
+
- a `FastAPI` instance
|
|
463
|
+
- an injectable `FastAPIServer`
|
|
464
|
+
- and a final `boot` call with the root module, which will return an `Environment`
|
|
434
465
|
|
|
435
466
|
```python
|
|
436
|
-
|
|
437
|
-
|
|
467
|
+
fast_api = FastAPI() # so you can run it with uvivorn from command-line
|
|
468
|
+
|
|
469
|
+
@module(imports=[ServiceModule])
|
|
470
|
+
class Module:
|
|
438
471
|
def __init__(self):
|
|
439
472
|
pass
|
|
473
|
+
|
|
474
|
+
@create()
|
|
475
|
+
def create_server(self, service_manager: ServiceManager, component_registry: ComponentRegistry) -> FastAPIServer:
|
|
476
|
+
return FastAPIServer(fastapi, service_manager, component_registry)
|
|
440
477
|
|
|
441
|
-
server = FastAPIServer(host="0.0.0.0", port=8000)
|
|
442
478
|
|
|
443
|
-
|
|
479
|
+
environment = FastAPIServer.boot(Moudle, host="0.0.0.0", port=8000)
|
|
444
480
|
```
|
|
445
481
|
|
|
446
482
|
This setup will also expose all service interfaces decorated with the corresponding http decorators!
|
|
447
483
|
No need to add any FastAPI decorators, since the mapping is already done internally!
|
|
448
484
|
|
|
485
|
+
## Session
|
|
486
|
+
|
|
487
|
+
TODO
|
|
488
|
+
|
|
489
|
+
## Authorization
|
|
490
|
+
|
|
491
|
+
TODO
|
|
492
|
+
|
|
449
493
|
## Implementing Channels
|
|
450
494
|
|
|
451
495
|
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)
|
|
@@ -0,0 +1,155 @@
|
|
|
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_service import ServiceModule, delete, post, put, get, rest, Body
|
|
11
|
+
from aspyx_service.service import component, Component, Service, service
|
|
12
|
+
|
|
13
|
+
from aspyx.di import module
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Pydantic(BaseModel):
|
|
17
|
+
i: int
|
|
18
|
+
f: float
|
|
19
|
+
b: bool
|
|
20
|
+
s: str
|
|
21
|
+
|
|
22
|
+
str0 : str
|
|
23
|
+
str1: str
|
|
24
|
+
str2: str
|
|
25
|
+
str3: str
|
|
26
|
+
str4: str
|
|
27
|
+
str5: str
|
|
28
|
+
str6: str
|
|
29
|
+
str7: str
|
|
30
|
+
str8: str
|
|
31
|
+
str9: str
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class Data:
|
|
36
|
+
i: int
|
|
37
|
+
f: float
|
|
38
|
+
b: bool
|
|
39
|
+
s: str
|
|
40
|
+
|
|
41
|
+
str0: str
|
|
42
|
+
str1: str
|
|
43
|
+
str2: str
|
|
44
|
+
str3: str
|
|
45
|
+
str4: str
|
|
46
|
+
str5: str
|
|
47
|
+
str6: str
|
|
48
|
+
str7: str
|
|
49
|
+
str8: str
|
|
50
|
+
str9: str
|
|
51
|
+
|
|
52
|
+
class PydanticAndData(BaseModel):
|
|
53
|
+
p: Pydantic
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class DataAndPydantic:
|
|
57
|
+
d: Data
|
|
58
|
+
|
|
59
|
+
# service
|
|
60
|
+
|
|
61
|
+
@service(name="test-service", description="cool")
|
|
62
|
+
class TestService(Service):
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def hello(self, message: str) -> str:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def throw(self, message: str) -> str:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
@abstractmethod
|
|
72
|
+
def data(self, data: Data) -> Data:
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
@abstractmethod
|
|
76
|
+
def pydantic(self, data: Pydantic) -> Pydantic:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
@service(name="test-async-service", description="cool")
|
|
80
|
+
class TestAsyncService(Service):
|
|
81
|
+
@abstractmethod
|
|
82
|
+
async def hello(self, message: str) -> str:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
@abstractmethod
|
|
86
|
+
async def data(self, data: Data) -> Data:
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
async def pydantic(self, data: Pydantic) -> Pydantic:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
@service(name="test-rest-service", description="cool")
|
|
94
|
+
@rest("/api")
|
|
95
|
+
class TestRestService(Service):
|
|
96
|
+
@abstractmethod
|
|
97
|
+
@get("/hello/{message}")
|
|
98
|
+
def get(self, message: str) -> str:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
@put("/hello/{message}")
|
|
102
|
+
def put(self, message: str) -> str:
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
@post("/hello/{message}")
|
|
106
|
+
def post_pydantic(self, message: str, data: Body(Pydantic)) -> Pydantic:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
@post("/hello/{message}")
|
|
110
|
+
def post_data(self, message: str, data: Body(Data)) -> Data:
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
@delete("/hello/{message}")
|
|
114
|
+
def delete(self, message: str) -> str:
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
@service(name="test-async-rest-service", description="cool")
|
|
118
|
+
@rest("/async-api")
|
|
119
|
+
class TestAsyncRestService(Service):
|
|
120
|
+
@abstractmethod
|
|
121
|
+
@get("/hello/{message}")
|
|
122
|
+
async def get(self, message: str) -> str:
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
@put("/hello/{message}")
|
|
126
|
+
async def put(self, message: str) -> str:
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
@post("/hello/{message}")
|
|
130
|
+
async def post_pydantic(self, message: str, data: Body(Pydantic)) -> Pydantic:
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
@post("/hello/{message}")
|
|
134
|
+
async def post_data(self, message: str, data: Body(Data)) -> Data:
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
@delete("/hello/{message}")
|
|
138
|
+
async def delete(self, message: str) -> str:
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
@component(services =[
|
|
142
|
+
TestService,
|
|
143
|
+
TestAsyncService,
|
|
144
|
+
TestRestService,
|
|
145
|
+
TestAsyncRestService
|
|
146
|
+
])
|
|
147
|
+
class TestComponent(Component): # pylint: disable=abstract-method
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
# module
|
|
151
|
+
|
|
152
|
+
@module(imports=[ServiceModule])
|
|
153
|
+
class ClientModule:
|
|
154
|
+
def __init__(self):
|
|
155
|
+
pass
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
the server hosting the test services
|
|
3
|
+
"""
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from fastapi import FastAPI
|
|
8
|
+
|
|
9
|
+
from aspyx_service import FastAPIServer, RequestContext
|
|
10
|
+
from server import ServerModule
|
|
11
|
+
from aspyx.util import Logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
Logger.configure(default_level=logging.DEBUG, levels={
|
|
15
|
+
"httpx": logging.ERROR,
|
|
16
|
+
"aspyx.di": logging.ERROR,
|
|
17
|
+
"aspyx.di.aop": logging.ERROR,
|
|
18
|
+
"aspyx.service": logging.ERROR
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
PORT = int(os.getenv("FAST_API_PORT", "8000"))
|
|
22
|
+
|
|
23
|
+
app = FastAPI()
|
|
24
|
+
|
|
25
|
+
app.add_middleware(RequestContext)
|
|
26
|
+
#app.add_middleware(TokenContextMiddleware)
|
|
27
|
+
|
|
28
|
+
ServerModule.fastapi = app
|
|
29
|
+
|
|
30
|
+
FastAPIServer.boot(ServerModule, host="0.0.0.0", port=PORT, start_thread= False)
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
import uvicorn
|
|
34
|
+
uvicorn.run("main:app", host="0.0.0.0", port=PORT, reload=True, log_level="warning", access_log=False)
|
{aspyx_service-0.10.3/performance_tests → aspyx_service-0.10.5/performance-test}/performance-test.py
RENAMED
|
@@ -2,22 +2,97 @@
|
|
|
2
2
|
Tests
|
|
3
3
|
"""
|
|
4
4
|
import asyncio
|
|
5
|
-
import logging
|
|
6
5
|
import threading
|
|
7
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_service import ConsulComponentRegistry, SessionManager
|
|
14
|
+
|
|
15
|
+
from aspyx.di import module, Environment, create
|
|
16
|
+
from aspyx.di.aop import advice, around, methods, Invocation
|
|
17
|
+
from aspyx.util import Logger
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
from aspyx_service.service import ServiceManager, ComponentRegistry, Channel
|
|
21
|
+
|
|
22
|
+
Logger.configure(default_level=logging.INFO, levels={
|
|
23
|
+
"httpx": logging.CRITICAL,
|
|
24
|
+
"aspyx.di": logging.INFO,
|
|
25
|
+
"aspyx.di.aop": logging.INFO,
|
|
26
|
+
"aspyx.service": logging.INFO
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
from client import TestService, TestRestService, Pydantic, Data, TestAsyncRestService, TestAsyncService, ClientModule
|
|
30
|
+
|
|
31
|
+
# main
|
|
32
|
+
|
|
33
|
+
@advice
|
|
34
|
+
class ChannelAdvice:
|
|
35
|
+
def __init__(self):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
@around(methods().named("customize").of_type(Channel))
|
|
39
|
+
def customize_channel(self, invocation: Invocation):
|
|
40
|
+
channel = cast(Channel, invocation.args[0])
|
|
8
41
|
|
|
9
|
-
|
|
42
|
+
channel.select_round_robin() # or select_first_url()
|
|
10
43
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
44
|
+
return invocation.proceed()
|
|
45
|
+
|
|
46
|
+
@module(imports=[ClientModule])
|
|
47
|
+
class TestModule:
|
|
48
|
+
def __init__(self):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
@create()
|
|
52
|
+
def create_session_storage(self) -> SessionManager.Storage:
|
|
53
|
+
return SessionManager.InMemoryStorage(max_size=1000, ttl=3600)
|
|
54
|
+
|
|
55
|
+
@create()
|
|
56
|
+
def create_registry(self) -> ComponentRegistry:
|
|
57
|
+
return ConsulComponentRegistry(port=8000, consul=Consul(host="localhost", port=8500))
|
|
58
|
+
|
|
59
|
+
def boot() -> ServiceManager:
|
|
60
|
+
environment = Environment(TestModule)
|
|
61
|
+
|
|
62
|
+
service_manager = environment.get(ServiceManager)
|
|
63
|
+
|
|
64
|
+
return service_manager
|
|
14
65
|
|
|
15
66
|
T = TypeVar("T")
|
|
16
67
|
|
|
17
68
|
# main
|
|
18
69
|
|
|
19
|
-
|
|
20
|
-
|
|
70
|
+
lorem_ipsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
|
|
71
|
+
|
|
72
|
+
pydantic = Pydantic(i=1, f=1.0, b=True, s="s",
|
|
73
|
+
str0=lorem_ipsum,
|
|
74
|
+
str1=lorem_ipsum,
|
|
75
|
+
str2=lorem_ipsum,
|
|
76
|
+
str3=lorem_ipsum,
|
|
77
|
+
str4=lorem_ipsum,
|
|
78
|
+
str5=lorem_ipsum,
|
|
79
|
+
str6=lorem_ipsum,
|
|
80
|
+
str7=lorem_ipsum,
|
|
81
|
+
str8=lorem_ipsum,
|
|
82
|
+
str9=lorem_ipsum
|
|
83
|
+
)
|
|
84
|
+
data = Data(i=1, f=1.0, b=True, s="s",
|
|
85
|
+
str0=lorem_ipsum,
|
|
86
|
+
str1=lorem_ipsum,
|
|
87
|
+
str2=lorem_ipsum,
|
|
88
|
+
str3=lorem_ipsum,
|
|
89
|
+
str4=lorem_ipsum,
|
|
90
|
+
str5=lorem_ipsum,
|
|
91
|
+
str6=lorem_ipsum,
|
|
92
|
+
str7=lorem_ipsum,
|
|
93
|
+
str8=lorem_ipsum,
|
|
94
|
+
str9=lorem_ipsum
|
|
95
|
+
)
|
|
21
96
|
|
|
22
97
|
def run_loops(name: str, loops: int, type: Type[T], instance: T, callable: Callable[[T], None]):
|
|
23
98
|
start = time.perf_counter()
|
|
@@ -43,6 +118,8 @@ def run_threaded_async_loops(name: str, loops: int, n_threads: int, type: Type[
|
|
|
43
118
|
threads = []
|
|
44
119
|
|
|
45
120
|
def worker(thread_id: int):
|
|
121
|
+
#print(f"worker {thread_id} running on thread {threading.current_thread().name}")
|
|
122
|
+
|
|
46
123
|
loop = asyncio.new_event_loop()
|
|
47
124
|
asyncio.set_event_loop(loop)
|
|
48
125
|
|
|
@@ -73,6 +150,8 @@ def run_threaded_sync_loops(name: str, loops: int, n_threads: int, type: Type[T
|
|
|
73
150
|
threads = []
|
|
74
151
|
|
|
75
152
|
def worker(thread_id: int):
|
|
153
|
+
#print(f"worker {thread_id} running on thread {threading.current_thread().name}")
|
|
154
|
+
|
|
76
155
|
loop = asyncio.new_event_loop()
|
|
77
156
|
asyncio.set_event_loop(loop)
|
|
78
157
|
|
|
@@ -99,10 +178,13 @@ def run_threaded_sync_loops(name: str, loops: int, n_threads: int, type: Type[T
|
|
|
99
178
|
|
|
100
179
|
print(f"{name} {loops} in {n_threads} threads: {took} ms, avg: {avg_ms}ms")
|
|
101
180
|
|
|
181
|
+
manager = boot()
|
|
182
|
+
|
|
102
183
|
async def main():
|
|
184
|
+
print("start tests...")
|
|
185
|
+
|
|
103
186
|
# get service manager
|
|
104
187
|
|
|
105
|
-
manager = service_manager()
|
|
106
188
|
loops = 1000
|
|
107
189
|
|
|
108
190
|
# tests
|
|
@@ -131,7 +213,7 @@ async def main():
|
|
|
131
213
|
# async
|
|
132
214
|
|
|
133
215
|
await run_async_loops("async rest", loops, TestAsyncRestService, manager.get_service(TestAsyncRestService, preferred_channel="rest"),
|
|
134
|
-
|
|
216
|
+
lambda service: service.get("world"))
|
|
135
217
|
await run_async_loops("async json", loops, TestAsyncService, manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
|
|
136
218
|
lambda service: service.hello("world"))
|
|
137
219
|
await run_async_loops("async msgpack", loops, TestAsyncService, manager.get_service(TestAsyncService, preferred_channel="dispatch-msgpack"),
|
|
@@ -166,7 +248,7 @@ async def main():
|
|
|
166
248
|
# sync
|
|
167
249
|
|
|
168
250
|
run_threaded_sync_loops("threaded sync json, 1 thread", loops, 1, TestService,
|
|
169
|
-
manager.get_service(
|
|
251
|
+
manager.get_service(TestService, preferred_channel="dispatch-json"),
|
|
170
252
|
lambda service: service.hello("world"))
|
|
171
253
|
run_threaded_sync_loops("threaded sync json, 2 thread", loops, 2, TestService,
|
|
172
254
|
manager.get_service(TestService, preferred_channel="dispatch-json"),
|
|
@@ -199,7 +281,5 @@ async def main():
|
|
|
199
281
|
manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
|
|
200
282
|
lambda service: service.hello("world"))
|
|
201
283
|
|
|
202
|
-
|
|
203
284
|
if __name__ == "__main__":
|
|
204
285
|
asyncio.run(main())
|
|
205
|
-
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 1 --reload --log-level warning
|