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.

Files changed (34) hide show
  1. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/PKG-INFO +3 -2
  2. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/main.py +4 -4
  3. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/performance-test.py +28 -1
  4. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/server.py +6 -7
  5. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/pyproject.toml +4 -3
  6. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/__init__.py +5 -0
  7. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/channels.py +30 -3
  8. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/healthcheck.py +2 -2
  9. aspyx_service-0.11.0/src/aspyx_service/protobuf.py +1083 -0
  10. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/restchannel.py +23 -3
  11. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/server.py +82 -32
  12. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/service.py +9 -4
  13. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/tests/common.py +60 -44
  14. aspyx_service-0.11.0/tests/other.py +8 -0
  15. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/tests/test_async_service.py +16 -8
  16. aspyx_service-0.11.0/tests/test_proto.py +156 -0
  17. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/tests/test_service.py +33 -13
  18. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/.gitignore +0 -0
  19. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/LICENSE +0 -0
  20. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/README.md +0 -0
  21. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/__init__.py +0 -0
  22. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/client.py +0 -0
  23. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/config.yaml +0 -0
  24. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/readme.txt +0 -0
  25. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/start_server_8000.sh +0 -0
  26. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/performance-test/start_server_8001.sh +0 -0
  27. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/authorization.py +0 -0
  28. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/registries.py +0 -0
  29. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/src/aspyx_service/session.py +0 -0
  30. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/tests/__init__.py +0 -0
  31. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/tests/config.yaml +0 -0
  32. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/tests/test_healthcheck.py +0 -0
  33. {aspyx_service-0.10.7 → aspyx_service-0.11.0}/tests/test_jwt.py +0 -0
  34. {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.10.7
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.6.0
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.DEBUG, levels={
14
+ Logger.configure(default_level=logging.INFO, levels={
15
15
  "httpx": logging.ERROR,
16
- "aspyx.di": logging.ERROR,
17
- "aspyx.di.aop": logging.ERROR,
18
- "aspyx.service": logging.ERROR
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, Dict, cast
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.10.7"
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.6.0",
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
- elif 'missing' in www_auth:
426
+
427
+ if 'missing' in www_auth:
401
428
  raise MissingTokenException() from e
402
- else:
403
- raise InvalidTokenException() from e
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 @injectable can be created by an Environment.
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 `@on_init` will be called when the instance is created.
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)