aspyx-service 0.10.3__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 (35) hide show
  1. {aspyx_service-0.10.3 → aspyx_service-0.10.4}/PKG-INFO +31 -4
  2. {aspyx_service-0.10.3 → aspyx_service-0.10.4}/README.md +28 -2
  3. aspyx_service-0.10.4/performance-test/client.py +154 -0
  4. aspyx_service-0.10.4/performance-test/main.py +23 -0
  5. {aspyx_service-0.10.3/performance_tests → aspyx_service-0.10.4/performance-test}/performance-test.py +86 -12
  6. aspyx_service-0.10.4/performance-test/readme.txt +1 -0
  7. aspyx_service-0.10.4/performance-test/server.py +154 -0
  8. aspyx_service-0.10.4/performance-test/start_server_8000.sh +3 -0
  9. aspyx_service-0.10.4/performance-test/start_server_8001.sh +3 -0
  10. {aspyx_service-0.10.3 → aspyx_service-0.10.4}/pyproject.toml +13 -2
  11. {aspyx_service-0.10.3 → aspyx_service-0.10.4}/src/aspyx_service/__init__.py +28 -4
  12. aspyx_service-0.10.4/src/aspyx_service/authorization.py +135 -0
  13. aspyx_service-0.10.4/src/aspyx_service/channels.py +471 -0
  14. {aspyx_service-0.10.3 → aspyx_service-0.10.4}/src/aspyx_service/healthcheck.py +1 -1
  15. {aspyx_service-0.10.3 → aspyx_service-0.10.4}/src/aspyx_service/registries.py +5 -5
  16. {aspyx_service-0.10.3 → aspyx_service-0.10.4}/src/aspyx_service/restchannel.py +15 -18
  17. {aspyx_service-0.10.3 → aspyx_service-0.10.4}/src/aspyx_service/serialization.py +3 -3
  18. {aspyx_service-0.10.3 → aspyx_service-0.10.4}/src/aspyx_service/server.py +139 -69
  19. {aspyx_service-0.10.3 → aspyx_service-0.10.4}/src/aspyx_service/service.py +47 -12
  20. aspyx_service-0.10.4/src/aspyx_service/session.py +97 -0
  21. aspyx_service-0.10.4/tests/__init__.py +0 -0
  22. {aspyx_service-0.10.3 → aspyx_service-0.10.4}/tests/common.py +139 -31
  23. aspyx_service-0.10.4/tests/config.yaml +16 -0
  24. aspyx_service-0.10.4/tests/test_async_service.py +53 -0
  25. aspyx_service-0.10.4/tests/test_healthcheck.py +51 -0
  26. aspyx_service-0.10.4/tests/test_jwt.py +403 -0
  27. {aspyx_service-0.10.3 → aspyx_service-0.10.4}/tests/test_serialization.py +8 -6
  28. aspyx_service-0.10.4/tests/test_service.py +80 -0
  29. aspyx_service-0.10.3/src/aspyx_service/channels.py +0 -282
  30. aspyx_service-0.10.3/tests/test_async_service.py +0 -56
  31. aspyx_service-0.10.3/tests/test_service.py +0 -82
  32. {aspyx_service-0.10.3 → aspyx_service-0.10.4}/.gitignore +0 -0
  33. {aspyx_service-0.10.3 → aspyx_service-0.10.4}/LICENSE +0 -0
  34. {aspyx_service-0.10.3/tests → aspyx_service-0.10.4/performance-test}/__init__.py +0 -0
  35. {aspyx_service-0.10.3/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.3
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
 
@@ -166,10 +169,24 @@ environment = server.boot(Module)
166
169
  Of course, service can also be called locally. In case of multiple possible channels, a keyword argument is used to
167
170
  determine a specific channel. As a local channel has the name "local", the appropriate call is:
168
171
 
172
+ **Example**:
173
+
169
174
  ```python
170
175
  service = service_manager.get_service(TestService, preferred_channel="local")
171
176
  ```
172
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
+
173
190
  ## Features
174
191
 
175
192
  The library offers:
@@ -352,12 +369,14 @@ Channels implement the possible transport layer protocols. In the sense of a dyn
352
369
  Several channels are implemented:
353
370
 
354
371
  - `dispatch-json`
355
- channel that dispatches generic `Request` objects via a `invoke` POST-call
372
+ channel that posts generic `Request` objects via a `invoke` POST-call
356
373
  - `dispatch-msgpack`
357
- 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
358
375
  - `rest`
359
376
  channel that executes regular rest-calls as defined by a couple of decorators.
360
377
 
378
+ The `dispatch`channels have the big advantage, that you don`t have to deal with additional http decorators!
379
+
361
380
  All channels react on changed URLs as provided by the component registry.
362
381
 
363
382
  A so called `URLSelector` is used internally to provide URLs for every single call. Two subclasses exist that offer a different logic
@@ -482,6 +501,14 @@ class Module():
482
501
  This setup will also expose all service interfaces decorated with the corresponding http decorators!
483
502
  No need to add any FastAPI decorators, since the mapping is already done internally!
484
503
 
504
+ ## Session
505
+
506
+ TODO
507
+
508
+ ## Authorization
509
+
510
+ TODO
511
+
485
512
  ## Implementing Channels
486
513
 
487
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
 
@@ -130,10 +132,24 @@ environment = server.boot(Module)
130
132
  Of course, service can also be called locally. In case of multiple possible channels, a keyword argument is used to
131
133
  determine a specific channel. As a local channel has the name "local", the appropriate call is:
132
134
 
135
+ **Example**:
136
+
133
137
  ```python
134
138
  service = service_manager.get_service(TestService, preferred_channel="local")
135
139
  ```
136
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
+
137
153
  ## Features
138
154
 
139
155
  The library offers:
@@ -316,12 +332,14 @@ Channels implement the possible transport layer protocols. In the sense of a dyn
316
332
  Several channels are implemented:
317
333
 
318
334
  - `dispatch-json`
319
- channel that dispatches generic `Request` objects via a `invoke` POST-call
335
+ channel that posts generic `Request` objects via a `invoke` POST-call
320
336
  - `dispatch-msgpack`
321
- 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
322
338
  - `rest`
323
339
  channel that executes regular rest-calls as defined by a couple of decorators.
324
340
 
341
+ The `dispatch`channels have the big advantage, that you don`t have to deal with additional http decorators!
342
+
325
343
  All channels react on changed URLs as provided by the component registry.
326
344
 
327
345
  A so called `URLSelector` is used internally to provide URLs for every single call. Two subclasses exist that offer a different logic
@@ -446,6 +464,14 @@ class Module():
446
464
  This setup will also expose all service interfaces decorated with the corresponding http decorators!
447
465
  No need to add any FastAPI decorators, since the mapping is already done internally!
448
466
 
467
+ ## Session
468
+
469
+ TODO
470
+
471
+ ## Authorization
472
+
473
+ TODO
474
+
449
475
  ## Implementing Channels
450
476
 
451
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)
@@ -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)
@@ -2,22 +2,91 @@
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.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()
8
41
 
9
- from typing import Callable, TypeVar, Type, Awaitable, Any
42
+ return invocation.proceed()
10
43
 
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
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
14
59
 
15
60
  T = TypeVar("T")
16
61
 
17
62
  # main
18
63
 
19
- pydantic = Pydantic(i=1, f=1.0, b=True, s="s")
20
- data = Data(i=1, f=1.0, b=True, s="s")
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
+ )
21
90
 
22
91
  def run_loops(name: str, loops: int, type: Type[T], instance: T, callable: Callable[[T], None]):
23
92
  start = time.perf_counter()
@@ -43,6 +112,8 @@ def run_threaded_async_loops(name: str, loops: int, n_threads: int, type: Type[
43
112
  threads = []
44
113
 
45
114
  def worker(thread_id: int):
115
+ #print(f"worker {thread_id} running on thread {threading.current_thread().name}")
116
+
46
117
  loop = asyncio.new_event_loop()
47
118
  asyncio.set_event_loop(loop)
48
119
 
@@ -73,6 +144,8 @@ def run_threaded_sync_loops(name: str, loops: int, n_threads: int, type: Type[T
73
144
  threads = []
74
145
 
75
146
  def worker(thread_id: int):
147
+ #print(f"worker {thread_id} running on thread {threading.current_thread().name}")
148
+
76
149
  loop = asyncio.new_event_loop()
77
150
  asyncio.set_event_loop(loop)
78
151
 
@@ -99,10 +172,13 @@ def run_threaded_sync_loops(name: str, loops: int, n_threads: int, type: Type[T
99
172
 
100
173
  print(f"{name} {loops} in {n_threads} threads: {took} ms, avg: {avg_ms}ms")
101
174
 
175
+ manager = boot()
176
+
102
177
  async def main():
178
+ print("start tests...")
179
+
103
180
  # get service manager
104
181
 
105
- manager = service_manager()
106
182
  loops = 1000
107
183
 
108
184
  # tests
@@ -166,7 +242,7 @@ async def main():
166
242
  # sync
167
243
 
168
244
  run_threaded_sync_loops("threaded sync json, 1 thread", loops, 1, TestService,
169
- manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
245
+ manager.get_service(TestService, preferred_channel="dispatch-json"),
170
246
  lambda service: service.hello("world"))
171
247
  run_threaded_sync_loops("threaded sync json, 2 thread", loops, 2, TestService,
172
248
  manager.get_service(TestService, preferred_channel="dispatch-json"),
@@ -199,7 +275,5 @@ async def main():
199
275
  manager.get_service(TestAsyncService, preferred_channel="dispatch-json"),
200
276
  lambda service: service.hello("world"))
201
277
 
202
-
203
278
  if __name__ == "__main__":
204
- asyncio.run(main())
205
-
279
+ asyncio.run(main())
@@ -0,0 +1 @@
1
+ uvicorn main:app --host 0.0.0.0 --port 8000 --workers 1 --reload --log-level warning
@@ -0,0 +1,154 @@
1
+ """
2
+ Tests
3
+ """
4
+ import os
5
+ from typing import Optional
6
+
7
+ from consul import Consul
8
+
9
+ from aspyx.di import on_running, module, create
10
+ from aspyx.di.aop import Invocation, advice, error
11
+ from aspyx.exception import handle, ExceptionManager
12
+ from aspyx_service import HealthCheckManager, ServiceModule, ConsulComponentRegistry
13
+ from aspyx_service.service import ChannelAddress, Server, \
14
+ component_services, AbstractComponent, implementation, health, ComponentRegistry
15
+
16
+ from client import ClientModule, TestService, TestAsyncService, Data, Pydantic, TestRestService, TestAsyncRestService, TestComponent
17
+
18
+
19
+ # implementation classes
20
+
21
+ @implementation()
22
+ class TestServiceImpl(TestService):
23
+ def __init__(self):
24
+ pass
25
+
26
+ def hello(self, message: str) -> str:
27
+ return message
28
+
29
+ def throw(self, message: str) -> str:
30
+ raise Exception(message)
31
+
32
+ def data(self, data: Data) -> Data:
33
+ return data
34
+
35
+ def pydantic(self, data: Pydantic) -> Pydantic:
36
+ return data
37
+
38
+ @implementation()
39
+ class TestAsyncServiceImpl(TestAsyncService):
40
+ def __init__(self):
41
+ pass
42
+
43
+ async def hello(self, message: str) -> str:
44
+ return message
45
+
46
+ async def data(self, data: Data) -> Data:
47
+ return data
48
+
49
+ async def pydantic(self, data: Pydantic) -> Pydantic:
50
+ return data
51
+
52
+ @implementation()
53
+ class TestRestServiceImpl(TestRestService):
54
+ def __init__(self):
55
+ pass
56
+
57
+ def get(self, message: str) -> str:
58
+ return message
59
+
60
+ def put(self, message: str) -> str:
61
+ return message
62
+
63
+ def post_pydantic(self, message: str, data: Pydantic) -> Pydantic:
64
+ return data
65
+
66
+ def post_data(self, message: str, data: Data) -> Data:
67
+ return data
68
+
69
+ def delete(self, message: str) -> str:
70
+ return message
71
+
72
+ @implementation()
73
+ class TestAsyncRestServiceImpl(TestAsyncRestService):
74
+ def __init__(self):
75
+ pass
76
+
77
+ async def get(self, message: str) -> str:
78
+ return message
79
+
80
+ async def put(self, message: str) -> str:
81
+ return message
82
+
83
+ async def post_pydantic(self, message: str, data: Pydantic) -> Pydantic:
84
+ return data
85
+
86
+ async def post_data(self, message: str, data: Data) -> Data:
87
+ return data
88
+
89
+ async def delete(self, message: str) -> str:
90
+ return message
91
+
92
+ @implementation()
93
+ @health("/health")
94
+ @advice
95
+ class TestComponentImpl(AbstractComponent, TestComponent):
96
+ # constructor
97
+
98
+ def __init__(self):
99
+ super().__init__()
100
+
101
+ self.health_check_manager : Optional[HealthCheckManager] = None
102
+ self.exception_manager = ExceptionManager()
103
+
104
+ # exception handler
105
+
106
+ @handle()
107
+ def handle_exception(self, exception: Exception):
108
+ print("caught exception!")
109
+ return exception
110
+
111
+ # aspects
112
+
113
+ @error(component_services(TestComponent))
114
+ def catch(self, invocation: Invocation):
115
+ return self.exception_manager.handle(invocation.exception)
116
+
117
+ # lifecycle
118
+
119
+ @on_running()
120
+ def setup_exception_handlers(self):
121
+ self.exception_manager.collect_handlers(self)
122
+
123
+ # implement
124
+
125
+ async def get_health(self) -> HealthCheckManager.Health:
126
+ return HealthCheckManager.Health()
127
+
128
+ def get_addresses(self, port: int) -> list[ChannelAddress]:
129
+ return [
130
+ ChannelAddress("rest", f"http://{Server.get_local_ip()}:{port}"),
131
+ ChannelAddress("dispatch-json", f"http://{Server.get_local_ip()}:{port}"),
132
+ ChannelAddress("dispatch-msgpack", f"http://{Server.get_local_ip()}:{port}")
133
+ ]
134
+
135
+ def startup(self) -> None:
136
+ print("### startup")
137
+
138
+ def shutdown(self) -> None:
139
+ print("### shutdown")
140
+
141
+ # module
142
+
143
+ @module(imports=[ClientModule, ServiceModule])
144
+ class ServerModule:
145
+ def __init__(self):
146
+ pass
147
+
148
+ #@create()
149
+ #def create_yaml_source(self) -> YamlConfigurationSource:
150
+ # return YamlConfigurationSource(f"{Path(__file__).parent}/config.yaml")
151
+
152
+ @create()
153
+ def create_registry(self) -> ComponentRegistry:
154
+ return ConsulComponentRegistry(port=int(os.getenv("FAST_API_PORT", 8000)), consul=Consul(host="localhost", port=8500))
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+ export FAST_API_PORT=8000
3
+ uvicorn main:app --host 0.0.0.0 --port $FAST_API_PORT --workers 1 --log-level warning
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+ export FAST_API_PORT=8001
3
+ uvicorn main:app --host 0.0.0.0 --port $FAST_API_PORT --workers 1 --log-level warning
@@ -2,18 +2,19 @@
2
2
 
3
3
  [project]
4
4
  name = "aspyx_service"
5
- version = "0.10.3"
5
+ version = "0.10.4"
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.5.1",
12
+ "aspyx>=1.5.3",
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
+ "cachetools~= 5.5.2",
17
18
  "uvicorn[standard]"
18
19
  ]
19
20
 
@@ -27,5 +28,15 @@ source = "src"
27
28
  [tool.hatch.build.targets.wheel]
28
29
  packages = ["src/aspyx_service"]
29
30
 
31
+ [tool.hatch.envs.test]
32
+ dependencies = [
33
+ ".",
34
+ "pytest",
35
+ "pytest-cov",
36
+ "pytest-asyncio",
37
+ "anyio",
38
+ "PyJWT"
39
+ ]
40
+
30
41
  [tool.hatch.metadata]
31
42
  allow-direct-references = true