aspyx-service 0.10.7__tar.gz → 0.11.0__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.7 → aspyx_service-0.11.0}/PKG-INFO +3 -2
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/main.py +4 -4
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/performance-test.py +28 -1
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/server.py +6 -7
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/pyproject.toml +4 -3
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/__init__.py +5 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/channels.py +30 -3
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/healthcheck.py +2 -2
- aspyx_service-0.11.0/src/aspyx_service/protobuf.py +1083 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/restchannel.py +23 -3
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/server.py +82 -32
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/service.py +9 -4
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/tests/common.py +60 -44
- aspyx_service-0.11.0/tests/other.py +8 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/tests/test_async_service.py +16 -8
- aspyx_service-0.11.0/tests/test_proto.py +156 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/tests/test_service.py +33 -13
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/.gitignore +0 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/LICENSE +0 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/README.md +0 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/__init__.py +0 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/client.py +0 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/config.yaml +0 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/readme.txt +0 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/start_server_8000.sh +0 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/start_server_8001.sh +0 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/authorization.py +0 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/registries.py +0 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/session.py +0 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/tests/__init__.py +0 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/tests/config.yaml +0 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/tests/test_healthcheck.py +0 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/tests/test_jwt.py +0 -0
- {aspyx_service-0.10.7 → aspyx_service-0.11.0}/tests/test_serialization.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aspyx_service
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
4
|
Summary: Aspyx Service framework
|
|
5
5
|
Author-email: Andreas Ernst <andreas.ernst7@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -26,10 +26,11 @@ 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.7.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
|
|
33
|
+
Requires-Dist: protobuf~=5.29.4
|
|
33
34
|
Requires-Dist: python-consul2~=0.1.5
|
|
34
35
|
Requires-Dist: uvicorn[standard]
|
|
35
36
|
Description-Content-Type: text/markdown
|
|
@@ -11,11 +11,11 @@ from server import ServerModule
|
|
|
11
11
|
from aspyx.util import Logger
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
Logger.configure(default_level=logging.
|
|
14
|
+
Logger.configure(default_level=logging.INFO, levels={
|
|
15
15
|
"httpx": logging.ERROR,
|
|
16
|
-
"aspyx.di": logging.
|
|
17
|
-
"aspyx.di.aop": logging.
|
|
18
|
-
"aspyx.service": logging.
|
|
16
|
+
"aspyx.di": logging.INFO,
|
|
17
|
+
"aspyx.di.aop": logging.INFO,
|
|
18
|
+
"aspyx.service": logging.INFO
|
|
19
19
|
})
|
|
20
20
|
|
|
21
21
|
PORT = int(os.getenv("FAST_API_PORT", "8000"))
|
|
@@ -6,7 +6,7 @@ import threading
|
|
|
6
6
|
import time
|
|
7
7
|
import logging
|
|
8
8
|
|
|
9
|
-
from typing import Callable, TypeVar, Type, Awaitable, Any,
|
|
9
|
+
from typing import Callable, TypeVar, Type, Awaitable, Any, cast
|
|
10
10
|
|
|
11
11
|
from consul import Consul
|
|
12
12
|
|
|
@@ -95,6 +95,8 @@ data = Data(i=1, f=1.0, b=True, s="s",
|
|
|
95
95
|
)
|
|
96
96
|
|
|
97
97
|
def run_loops(name: str, loops: int, type: Type[T], instance: T, callable: Callable[[T], None]):
|
|
98
|
+
callable(instance) # initialization
|
|
99
|
+
|
|
98
100
|
start = time.perf_counter()
|
|
99
101
|
for _ in range(loops):
|
|
100
102
|
callable(instance)
|
|
@@ -105,6 +107,8 @@ def run_loops(name: str, loops: int, type: Type[T], instance: T, callable: Call
|
|
|
105
107
|
print(f"run {name}, loops={loops}: avg={avg_ms:.3f} ms")
|
|
106
108
|
|
|
107
109
|
async def run_async_loops(name: str, loops: int, type: Type[T], instance: T, callable: Callable[[T], Awaitable[Any]]):
|
|
110
|
+
await callable(instance) # initialization
|
|
111
|
+
|
|
108
112
|
start = time.perf_counter()
|
|
109
113
|
for _ in range(loops):
|
|
110
114
|
await callable(instance)
|
|
@@ -192,12 +196,17 @@ async def main():
|
|
|
192
196
|
run_loops("rest", loops, TestRestService, manager.get_service(TestRestService, preferred_channel="rest"), lambda service: service.get("world"))
|
|
193
197
|
run_loops("json", loops, TestService, manager.get_service(TestService, preferred_channel="dispatch-json"), lambda service: service.hello("world"))
|
|
194
198
|
run_loops("msgpack", loops, TestService, manager.get_service(TestService, preferred_channel="dispatch-msgpack"), lambda service: service.hello("world"))
|
|
199
|
+
run_loops("protobuf", loops, TestService, manager.get_service(TestService, preferred_channel="dispatch-protobuf"),
|
|
200
|
+
lambda service: service.hello("world"))
|
|
195
201
|
|
|
196
202
|
# pydantic
|
|
197
203
|
|
|
198
204
|
run_loops("rest & pydantic", loops, TestRestService, manager.get_service(TestRestService, preferred_channel="rest"), lambda service: service.post_pydantic("hello", pydantic))
|
|
199
205
|
run_loops("json & pydantic", loops, TestService, manager.get_service(TestService, preferred_channel="dispatch-json"), lambda service: service.pydantic(pydantic))
|
|
200
206
|
run_loops("msgpack & pydantic", loops, TestService, manager.get_service(TestService, preferred_channel="dispatch-msgpack"), lambda service: service.pydantic(pydantic))
|
|
207
|
+
run_loops("protobuf & pydantic", loops, TestService,
|
|
208
|
+
manager.get_service(TestService, preferred_channel="dispatch-protobuf"),
|
|
209
|
+
lambda service: service.pydantic(pydantic))
|
|
201
210
|
|
|
202
211
|
# data class
|
|
203
212
|
|
|
@@ -209,6 +218,9 @@ async def main():
|
|
|
209
218
|
run_loops("msgpack & data", loops, TestService,
|
|
210
219
|
manager.get_service(TestService, preferred_channel="dispatch-msgpack"),
|
|
211
220
|
lambda service: service.data(data))
|
|
221
|
+
run_loops("protobuf & data", loops, TestService,
|
|
222
|
+
manager.get_service(TestService, preferred_channel="dispatch-protobuf"),
|
|
223
|
+
lambda service: service.data(data))
|
|
212
224
|
|
|
213
225
|
# async
|
|
214
226
|
|
|
@@ -218,6 +230,9 @@ async def main():
|
|
|
218
230
|
lambda service: service.hello("world"))
|
|
219
231
|
await run_async_loops("async msgpack", loops, TestAsyncService, manager.get_service(TestAsyncService, preferred_channel="dispatch-msgpack"),
|
|
220
232
|
lambda service: service.hello("world"))
|
|
233
|
+
await run_async_loops("async protobuf", loops, TestAsyncService,
|
|
234
|
+
manager.get_service(TestAsyncService, preferred_channel="dispatch-protobuf"),
|
|
235
|
+
lambda service: service.hello("world"))
|
|
221
236
|
|
|
222
237
|
# pydantic
|
|
223
238
|
|
|
@@ -229,6 +244,9 @@ async def main():
|
|
|
229
244
|
await run_async_loops("async msgpack & pydantic", loops, TestAsyncService,
|
|
230
245
|
manager.get_service(TestAsyncService, preferred_channel="dispatch-msgpack"),
|
|
231
246
|
lambda service: service.pydantic(pydantic))
|
|
247
|
+
await run_async_loops("async protobuf & pydantic", loops, TestAsyncService,
|
|
248
|
+
manager.get_service(TestAsyncService, preferred_channel="dispatch-protobuf"),
|
|
249
|
+
lambda service: service.pydantic(pydantic))
|
|
232
250
|
|
|
233
251
|
# data class
|
|
234
252
|
|
|
@@ -242,6 +260,9 @@ async def main():
|
|
|
242
260
|
await run_async_loops("async msgpack & data", loops, TestAsyncService,
|
|
243
261
|
manager.get_service(TestAsyncService, preferred_channel="dispatch-msgpack"),
|
|
244
262
|
lambda service: service.data(data))
|
|
263
|
+
await run_async_loops("async protobuf & data", loops, TestAsyncService,
|
|
264
|
+
manager.get_service(TestAsyncService, preferred_channel="dispatch-protobuf"),
|
|
265
|
+
lambda service: service.data(data))
|
|
245
266
|
|
|
246
267
|
# a real thread test
|
|
247
268
|
|
|
@@ -262,6 +283,9 @@ async def main():
|
|
|
262
283
|
run_threaded_sync_loops("threaded sync json, 16 thread", loops, 16, TestService,
|
|
263
284
|
manager.get_service(TestService, preferred_channel="dispatch-json"),
|
|
264
285
|
lambda service: service.hello("world"))
|
|
286
|
+
run_threaded_sync_loops("threaded sync protobuf, 16 thread", loops, 16, TestService,
|
|
287
|
+
manager.get_service(TestService, preferred_channel="dispatch-protobuf"),
|
|
288
|
+
lambda service: service.hello("world"))
|
|
265
289
|
|
|
266
290
|
# async
|
|
267
291
|
|
|
@@ -280,6 +304,9 @@ async def main():
|
|
|
280
304
|
run_threaded_async_loops("threaded async json, 16 thread", loops, 16, TestAsyncService,
|
|
281
305
|
manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
|
|
282
306
|
lambda service: service.hello("world"))
|
|
307
|
+
run_threaded_async_loops("threaded async protobuf, 16 thread", loops, 16, TestAsyncService,
|
|
308
|
+
manager.get_service(TestAsyncService, preferred_channel="dispatch-protobuf"),
|
|
309
|
+
lambda service: service.hello("world"))
|
|
283
310
|
|
|
284
311
|
if __name__ == "__main__":
|
|
285
312
|
asyncio.run(main())
|
|
@@ -7,7 +7,8 @@ from typing import Optional
|
|
|
7
7
|
from consul import Consul
|
|
8
8
|
from fastapi import FastAPI
|
|
9
9
|
|
|
10
|
-
from aspyx_service import HealthCheckManager, ServiceModule, ConsulComponentRegistry, SessionManager, FastAPIServer
|
|
10
|
+
from aspyx_service import HealthCheckManager, ServiceModule, ConsulComponentRegistry, SessionManager, FastAPIServer, \
|
|
11
|
+
ProtobufManager
|
|
11
12
|
|
|
12
13
|
from client import ClientModule, TestService, TestAsyncService, Data, Pydantic, TestRestService, TestAsyncRestService, TestComponent
|
|
13
14
|
|
|
@@ -17,9 +18,6 @@ from aspyx.di import on_running, module, create
|
|
|
17
18
|
from aspyx.di.aop import Invocation, advice, error
|
|
18
19
|
from aspyx.exception import handle, ExceptionManager
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
21
|
# implementation classes
|
|
24
22
|
|
|
25
23
|
@implementation()
|
|
@@ -133,7 +131,8 @@ class TestComponentImpl(AbstractComponent, TestComponent):
|
|
|
133
131
|
return [
|
|
134
132
|
ChannelAddress("rest", f"http://{Server.get_local_ip()}:{port}"),
|
|
135
133
|
ChannelAddress("dispatch-json", f"http://{Server.get_local_ip()}:{port}"),
|
|
136
|
-
ChannelAddress("dispatch-msgpack", f"http://{Server.get_local_ip()}:{port}")
|
|
134
|
+
ChannelAddress("dispatch-msgpack", f"http://{Server.get_local_ip()}:{port}"),
|
|
135
|
+
ChannelAddress("dispatch-protobuf", f"http://{Server.get_local_ip()}:{port}")
|
|
137
136
|
]
|
|
138
137
|
|
|
139
138
|
def startup(self) -> None:
|
|
@@ -156,8 +155,8 @@ class ServerModule:
|
|
|
156
155
|
# return YamlConfigurationSource(f"{Path(__file__).parent}/config.yaml")
|
|
157
156
|
|
|
158
157
|
@create()
|
|
159
|
-
def create_server(self, service_manager: ServiceManager, component_registry: ComponentRegistry) -> FastAPIServer:
|
|
160
|
-
return FastAPIServer(self.fastapi, service_manager, component_registry)
|
|
158
|
+
def create_server(self, service_manager: ServiceManager, component_registry: ComponentRegistry, protobuf_manager: ProtobufManager) -> FastAPIServer:
|
|
159
|
+
return FastAPIServer(self.fastapi, service_manager, component_registry, protobuf_manager)
|
|
161
160
|
|
|
162
161
|
@create()
|
|
163
162
|
def create_session_storage(self) -> SessionManager.Storage:
|
|
@@ -2,19 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
[project]
|
|
4
4
|
name = "aspyx_service"
|
|
5
|
-
version = "0.
|
|
5
|
+
version = "0.11.0"
|
|
6
6
|
description = "Aspyx Service framework"
|
|
7
7
|
authors = [{ name = "Andreas Ernst", email = "andreas.ernst7@gmail.com" }]
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
license = { file = "LICENSE" }
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
11
|
dependencies = [
|
|
12
|
-
"aspyx>=1.
|
|
12
|
+
"aspyx>=1.7.0",
|
|
13
13
|
"python-consul2~=0.1.5",
|
|
14
14
|
"fastapi~=0.115.13",
|
|
15
15
|
"httpx~=0.28.1",
|
|
16
16
|
"msgpack~=1.1.1",
|
|
17
|
-
"uvicorn[standard]"
|
|
17
|
+
"uvicorn[standard]",
|
|
18
|
+
"protobuf~=5.29.4"
|
|
18
19
|
]
|
|
19
20
|
|
|
20
21
|
[build-system]
|
|
@@ -12,6 +12,7 @@ from .healthcheck import health_checks, health_check, HealthCheckManager, Health
|
|
|
12
12
|
from .restchannel import RestChannel, post, get, put, delete, QueryParam, Body, rest
|
|
13
13
|
from .session import Session, SessionManager, SessionContext
|
|
14
14
|
from .authorization import AuthorizationManager, AbstractAuthorizationFactory
|
|
15
|
+
from .protobuf import ProtobufManager
|
|
15
16
|
|
|
16
17
|
@module()
|
|
17
18
|
class ServiceModule:
|
|
@@ -48,6 +49,10 @@ __all__ = [
|
|
|
48
49
|
"MissingTokenException",
|
|
49
50
|
"AuthorizationException",
|
|
50
51
|
|
|
52
|
+
# protobuf
|
|
53
|
+
|
|
54
|
+
"ProtobufManager",
|
|
55
|
+
|
|
51
56
|
# authorization
|
|
52
57
|
|
|
53
58
|
"AuthorizationManager",
|
|
@@ -196,6 +196,32 @@ class HTTPXChannel(Channel):
|
|
|
196
196
|
|
|
197
197
|
headers["Authorization"] = f"Bearer {token}"
|
|
198
198
|
|
|
199
|
+
### TEST
|
|
200
|
+
|
|
201
|
+
print_size = False
|
|
202
|
+
if print_size:
|
|
203
|
+
request = self.get_client().build_request(http_method, url, params=params, json=json, headers=headers, timeout=timeout, content=content)
|
|
204
|
+
# Measure body
|
|
205
|
+
body_size = len(request.content or b"")
|
|
206
|
+
|
|
207
|
+
# Measure headers (as raw bytes)
|
|
208
|
+
headers_size = sum(
|
|
209
|
+
len(k.encode()) + len(v.encode()) + 4 # ": " + "\r\n"
|
|
210
|
+
for k, v in request.headers.items()
|
|
211
|
+
) + 2 # final \r\n
|
|
212
|
+
|
|
213
|
+
# Optional: estimate request line
|
|
214
|
+
request_line = f"{request.method} {request.url.raw_path.decode()} HTTP/1.1\r\n".encode()
|
|
215
|
+
request_line_size = len(request_line)
|
|
216
|
+
|
|
217
|
+
# Total estimated size
|
|
218
|
+
total_size = request_line_size + headers_size + body_size
|
|
219
|
+
|
|
220
|
+
print(f"Request line: {request_line_size} bytes")
|
|
221
|
+
print(f"Headers: {headers_size} bytes")
|
|
222
|
+
print(f"Body: {body_size} bytes")
|
|
223
|
+
print(f"Total request size: {total_size} bytes")
|
|
224
|
+
|
|
199
225
|
try:
|
|
200
226
|
response = self.get_client().request(http_method, url, params=params, json=json, headers=headers, timeout=timeout, content=content)
|
|
201
227
|
response.raise_for_status()
|
|
@@ -397,10 +423,11 @@ class DispatchMSPackChannel(HTTPXChannel):
|
|
|
397
423
|
if "invalid_token" in www_auth:
|
|
398
424
|
if 'expired' in www_auth:
|
|
399
425
|
raise TokenExpiredException() from e
|
|
400
|
-
|
|
426
|
+
|
|
427
|
+
if 'missing' in www_auth:
|
|
401
428
|
raise MissingTokenException() from e
|
|
402
|
-
|
|
403
|
-
|
|
429
|
+
|
|
430
|
+
raise InvalidTokenException() from e
|
|
404
431
|
|
|
405
432
|
raise RemoteServiceException(str(e)) from e
|
|
406
433
|
except httpx.HTTPError as e:
|
|
@@ -15,7 +15,7 @@ from aspyx.reflection import Decorators, TypeDescriptor
|
|
|
15
15
|
|
|
16
16
|
def health_checks():
|
|
17
17
|
"""
|
|
18
|
-
Instances of classes that are annotated with @
|
|
18
|
+
Instances of classes that are annotated with @health_checks contain healt mehtods.
|
|
19
19
|
"""
|
|
20
20
|
def decorator(cls):
|
|
21
21
|
Decorators.add(cls, health_checks)
|
|
@@ -31,7 +31,7 @@ def health_checks():
|
|
|
31
31
|
|
|
32
32
|
def health_check(name="", cache = 0, fail_if_slower_than = 0):
|
|
33
33
|
"""
|
|
34
|
-
Methods annotated with `@
|
|
34
|
+
Methods annotated with `@health_check` specify health checks that will be executed.
|
|
35
35
|
"""
|
|
36
36
|
def decorator(func):
|
|
37
37
|
Decorators.add(func, health_check, name, cache, fail_if_slower_than)
|