aspyx-service 0.9.0__py3-none-any.whl → 0.10.1__py3-none-any.whl

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/__init__.py CHANGED
@@ -4,7 +4,7 @@ This module provides the core Aspyx service management framework allowing for se
4
4
 
5
5
  from aspyx.di import module
6
6
 
7
- from .service import ServiceException, Server, Channel, ComponentDescriptor, inject_service, ChannelAddress, ServiceAddress, ServiceManager, Component, Service, AbstractComponent, ComponentStatus, ComponentRegistry, implementation, health, component, service
7
+ from .service import ServiceException, Server, Channel, ComponentDescriptor, inject_service, ChannelAddress, ChannelInstances, ServiceManager, Component, Service, AbstractComponent, ComponentStatus, ComponentRegistry, implementation, health, component, service
8
8
  from .channels import HTTPXChannel, DispatchJSONChannel
9
9
  from .registries import ConsulComponentRegistry
10
10
  from .server import FastAPIServer
@@ -32,7 +32,7 @@ __all__ = [
32
32
  "ComponentDescriptor",
33
33
  "ComponentRegistry",
34
34
  "ChannelAddress",
35
- "ServiceAddress",
35
+ "ChannelInstances",
36
36
  "health",
37
37
  "component",
38
38
  "service",
@@ -41,6 +41,11 @@ __all__ = [
41
41
 
42
42
  # healthcheck
43
43
 
44
+ "health_checks",
45
+ "health_check",
46
+ "HealthStatus",
47
+ "HealthCheckManager",
48
+
44
49
  # serialization
45
50
 
46
51
  # "deserialize",
aspyx_service/channels.py CHANGED
@@ -3,6 +3,8 @@ Service management and dependency injection framework.
3
3
  """
4
4
  from __future__ import annotations
5
5
 
6
+ import json
7
+ from dataclasses import is_dataclass, asdict, fields
6
8
  from typing import Type, Optional, Any, Callable
7
9
 
8
10
  import msgpack
@@ -12,7 +14,7 @@ from pydantic import BaseModel
12
14
  from aspyx.reflection import DynamicProxy, TypeDescriptor
13
15
  from .service import ServiceManager
14
16
 
15
- from .service import ComponentDescriptor, ServiceAddress, ServiceException, channel, Channel, RemoteServiceException
17
+ from .service import ComponentDescriptor, ChannelInstances, ServiceException, channel, Channel, RemoteServiceException
16
18
  from .serialization import get_deserializer
17
19
 
18
20
 
@@ -24,10 +26,37 @@ class HTTPXChannel(Channel):
24
26
  "deserializers"
25
27
  ]
26
28
 
29
+ # class methods
30
+
31
+ @classmethod
32
+ def to_dict(cls, obj: Any) -> Any:
33
+ if isinstance(obj, BaseModel):
34
+ return obj.dict()
35
+
36
+
37
+ elif is_dataclass(obj):
38
+ return {
39
+ f.name: cls.to_dict(getattr(obj, f.name))
40
+
41
+ for f in fields(obj)
42
+ }
43
+
44
+ elif isinstance(obj, (list, tuple)):
45
+ return [cls.to_dict(item) for item in obj]
46
+
47
+ elif isinstance(obj, dict):
48
+ return {key: cls.to_dict(value) for key, value in obj.items()}
49
+
50
+ return obj
51
+
52
+ @classmethod
53
+ def to_json(cls, obj) -> str:
54
+ return json.dumps(cls.to_dict(obj))
55
+
27
56
  # constructor
28
57
 
29
- def __init__(self, name):
30
- super().__init__(name)
58
+ def __init__(self):
59
+ super().__init__()
31
60
 
32
61
  self.client: Optional[Client] = None
33
62
  self.async_client: Optional[AsyncClient] = None
@@ -49,7 +78,7 @@ class HTTPXChannel(Channel):
49
78
 
50
79
  # override
51
80
 
52
- def setup(self, component_descriptor: ComponentDescriptor, address: ServiceAddress):
81
+ def setup(self, component_descriptor: ComponentDescriptor, address: ChannelInstances):
53
82
  super().setup(component_descriptor, address)
54
83
 
55
84
  # remember service names
@@ -92,30 +121,35 @@ class Response(BaseModel):
92
121
 
93
122
  @channel("dispatch-json")
94
123
  class DispatchJSONChannel(HTTPXChannel):
124
+ """
125
+ A channel that calls a POST on th endpoint `ìnvoke` sending a request body containing information on the
126
+ called component, service and method and the arguments.
127
+ """
95
128
  # constructor
96
129
 
97
130
  def __init__(self):
98
- super().__init__("dispatch-json")
131
+ super().__init__()
99
132
 
100
133
  # internal
101
134
 
102
135
  # implement Channel
103
136
 
104
- def set_address(self, address: Optional[ServiceAddress]):
137
+ def set_address(self, address: Optional[ChannelInstances]):
105
138
  ServiceManager.logger.info("channel %s got an address %s", self.name, address)
106
139
 
107
140
  super().set_address(address)
108
141
 
109
- def setup(self, component_descriptor: ComponentDescriptor, address: ServiceAddress):
142
+ def setup(self, component_descriptor: ComponentDescriptor, address: ChannelInstances):
110
143
  super().setup(component_descriptor, address)
111
144
 
112
145
  def invoke(self, invocation: DynamicProxy.Invocation):
113
146
  service_name = self.service_names[invocation.type] # map type to registered service name
114
147
  request = Request(method=f"{self.component_descriptor.name}:{service_name}:{invocation.method.__name__}", args=invocation.args)
115
148
 
149
+ dict = self.to_dict(request)
116
150
  try:
117
151
  if self.client is not None:
118
- result = Response(**self.get_client().post(f"{self.get_url()}/invoke", json=request.dict(), timeout=30000.0).json())
152
+ result = Response(**self.get_client().post(f"{self.get_url()}/invoke", json=dict, timeout=30000.0).json())
119
153
  if result.exception is not None:
120
154
  raise RemoteServiceException(f"server side exception {result.exception}")
121
155
 
@@ -130,9 +164,10 @@ class DispatchJSONChannel(HTTPXChannel):
130
164
  request = Request(method=f"{self.component_descriptor.name}:{service_name}:{invocation.method.__name__}",
131
165
  args=invocation.args)
132
166
 
167
+ dict = self.to_dict(request)
133
168
  try:
134
169
  if self.async_client is not None:
135
- data = await self.async_client.post(f"{self.get_url()}/invoke", json=request.dict(), timeout=30000.0)
170
+ data = await self.async_client.post(f"{self.get_url()}/invoke", json=dict, timeout=30000.0)
136
171
  result = Response(**data.json())
137
172
  if result.exception is not None:
138
173
  raise RemoteServiceException(f"server side exception {result.exception}")
@@ -146,14 +181,17 @@ class DispatchJSONChannel(HTTPXChannel):
146
181
 
147
182
  @channel("dispatch-msgpack")
148
183
  class DispatchMSPackChannel(HTTPXChannel):
184
+ """
185
+ A channel that sends a POST on the ìnvoke `endpoint`with an msgpack encoded request body.
186
+ """
149
187
  # constructor
150
188
 
151
189
  def __init__(self):
152
- super().__init__("dispatch-msgpack")
190
+ super().__init__()
153
191
 
154
192
  # override
155
193
 
156
- def set_address(self, address: Optional[ServiceAddress]):
194
+ def set_address(self, address: Optional[ChannelInstances]):
157
195
  ServiceManager.logger.info("channel %s got an address %s", self.name, address)
158
196
 
159
197
  super().set_address(address)
@@ -164,7 +202,7 @@ class DispatchMSPackChannel(HTTPXChannel):
164
202
  args=invocation.args)
165
203
 
166
204
  try:
167
- packed = msgpack.packb(request.dict(), use_bin_type=True)
205
+ packed = msgpack.packb(self.to_dict(request), use_bin_type=True)
168
206
 
169
207
  response = self.get_client().post(
170
208
  f"{self.get_url()}/invoke",
@@ -189,7 +227,7 @@ class DispatchMSPackChannel(HTTPXChannel):
189
227
  args=invocation.args)
190
228
 
191
229
  try:
192
- packed = msgpack.packb(request.dict(), use_bin_type=True)
230
+ packed = msgpack.packb(self.to_dict(request), use_bin_type=True)
193
231
 
194
232
  response = await self.get_async_client().post(
195
233
  f"{self.get_url()}/invoke",
@@ -207,4 +245,3 @@ class DispatchMSPackChannel(HTTPXChannel):
207
245
 
208
246
  except Exception as e:
209
247
  raise ServiceException(f"msgpack exception: {e}") from e
210
-
@@ -9,7 +9,7 @@ import time
9
9
  from enum import Enum
10
10
  from typing import Any, Callable, Type, Optional
11
11
 
12
- from aspyx.di import Providers, ClassInstanceProvider, injectable, Environment, inject_environment, on_init
12
+ from aspyx.di import injectable, Environment, inject_environment, on_init
13
13
  from aspyx.reflection import Decorators, TypeDescriptor
14
14
 
15
15
 
@@ -31,7 +31,8 @@ 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 `@on_init` will be called when the instance is created.
35
+ """
35
36
  def decorator(func):
36
37
  Decorators.add(func, health_check, name, cache, fail_if_slower_than)
37
38
  return func
@@ -39,6 +40,13 @@ def health_check(name="", cache = 0, fail_if_slower_than = 0):
39
40
  return decorator
40
41
 
41
42
  class HealthStatus(Enum):
43
+ """
44
+ A enum specifying the health status of a service. The values are:
45
+
46
+ - `OK` service is healthy
47
+ - `WARNING` service has some problems
48
+ - `CRITICAL` service is unhealthy
49
+ """
42
50
  OK = 1
43
51
  WARNING = 2
44
52
  ERROR = 3
@@ -49,6 +57,9 @@ class HealthStatus(Enum):
49
57
 
50
58
  @injectable()
51
59
  class HealthCheckManager:
60
+ """
61
+ The health manager is able to run all registered health checks and is able to return an overall status.
62
+ """
52
63
  logger = logging.getLogger("aspyx.service.health")
53
64
 
54
65
  # local classes
@@ -112,8 +123,8 @@ class HealthCheckManager:
112
123
  return result
113
124
 
114
125
  class Health:
115
- def __init__(self):
116
- self.status = HealthStatus.OK
126
+ def __init__(self, status: HealthStatus = HealthStatus.OK):
127
+ self.status = status
117
128
  self.results : list[HealthCheckManager.Result] = []
118
129
 
119
130
  def to_dict(self):
@@ -135,6 +146,11 @@ class HealthCheckManager:
135
146
  # check
136
147
 
137
148
  async def check(self) -> HealthCheckManager.Health:
149
+ """
150
+ run all registered health checks and return an overall result.
151
+ Returns: the overall result.
152
+
153
+ """
138
154
  self.logger.info("Checking health...")
139
155
 
140
156
  health = HealthCheckManager.Health()
@@ -10,34 +10,38 @@ from urllib.parse import urlparse
10
10
 
11
11
  import consul
12
12
 
13
+ from aspyx.di.configuration import inject_value
13
14
  from aspyx.util import StringBuilder
14
15
  from aspyx.di import on_init
15
16
  from .healthcheck import HealthCheckManager, HealthStatus
16
17
 
17
18
  from .server import Server
18
- from .service import ComponentRegistry, Channel, ServiceAddress, ServiceManager, ComponentDescriptor, Component, ChannelAddress
19
+ from .service import ComponentRegistry, Channel, ChannelInstances, ServiceManager, ComponentDescriptor, Component, ChannelAddress
19
20
 
20
21
  class ConsulComponentRegistry(ComponentRegistry):
22
+ """
23
+ A specialized registry using consul.
24
+ A polling mechanism is used to identify changes in the component health.
25
+ """
21
26
  # constructor
22
27
 
23
- def __init__(self, port: int, consul_url: str):
28
+ def __init__(self, port: int, consul: consul.Consul):
24
29
  self.port = port
25
30
  self.ip = Server.get_local_ip()
26
31
  self.running = False
27
- self.consul = None
32
+ self.consul = consul
28
33
  self.watchdog = None
29
34
  self.interval = 5
30
35
  self.last_index = {}
31
- self.component_addresses : dict[str, dict[str, ServiceAddress]] = {} # comp -> channel -> address
36
+ self.component_addresses : dict[str, dict[str, ChannelInstances]] = {} # comp -> channel -> address
32
37
  self.watch_channels : list[Channel] = []
38
+ self.watchdog_interval = 5
33
39
 
34
- parsed = urlparse(consul_url)
40
+ # injections
35
41
 
36
- self.consul_host = parsed.hostname
37
- self.consul_port = parsed.port
38
-
39
- def make_consul(self, host: str, port: int) -> consul.Consul:
40
- return consul.Consul(host=host, port=port)
42
+ @inject_value("consul.watchdog.interval", default=5)
43
+ def set_interval(self, interval):
44
+ self.watchdog_interval = interval
41
45
 
42
46
  # lifecycle hooks
43
47
 
@@ -45,7 +49,6 @@ class ConsulComponentRegistry(ComponentRegistry):
45
49
  def setup(self):
46
50
  # create consul client
47
51
 
48
- self.consul = self.make_consul(host=self.consul_host, port=self.consul_port)
49
52
  self.running = True
50
53
 
51
54
  # start thread
@@ -53,7 +56,7 @@ class ConsulComponentRegistry(ComponentRegistry):
53
56
  self.watchdog = threading.Thread(target=self.watch_consul, daemon=True)
54
57
  self.watchdog.start()
55
58
 
56
- def inform_channels(self, old_address: ServiceAddress, new_address: Optional[ServiceAddress]):
59
+ def inform_channels(self, old_address: ChannelInstances, new_address: Optional[ChannelInstances]):
57
60
  for channel in self.watch_channels:
58
61
  if channel.address is old_address:
59
62
  channel.set_address(new_address)
@@ -107,19 +110,17 @@ class ConsulComponentRegistry(ComponentRegistry):
107
110
 
108
111
  # time to sleep
109
112
 
110
- time.sleep(5)
113
+ time.sleep(self.watchdog_interval)
111
114
 
112
115
  @abstractmethod
113
- def watch(self, channel: Channel):
116
+ def watch(self, channel: Channel) -> None:
114
117
  self.watch_channels.append(channel)
115
118
 
116
119
  #self.component_addresses[channel.component_descriptor.name] = {}
117
120
 
118
121
  # public
119
122
 
120
- def register_service(self, name, service_id, health: str, tags=None, meta=None):
121
- ip = "host.docker.internal" # TODO
122
-
123
+ def register_service(self, name, service_id, health: str, tags=None, meta=None) -> None:
123
124
  self.consul.agent.service.register(
124
125
  name=name,
125
126
  service_id=service_id,
@@ -128,13 +129,13 @@ class ConsulComponentRegistry(ComponentRegistry):
128
129
  tags=tags or [],
129
130
  meta=meta or {},
130
131
  check=consul.Check().http(
131
- url=f"http://{ip}:{self.port}{health}",
132
+ url=f"http://{self.ip}:{self.port}{health}",
132
133
  interval="10s",
133
134
  timeout="3s",
134
135
  deregister="5m")
135
136
  )
136
137
 
137
- def deregister(self, descriptor: ComponentDescriptor[Component]):
138
+ def deregister(self, descriptor: ComponentDescriptor[Component]) -> None:
138
139
  name = descriptor.name
139
140
 
140
141
  service_id = f"{self.ip}:{self.port}:{name}"
@@ -149,8 +150,8 @@ class ConsulComponentRegistry(ComponentRegistry):
149
150
 
150
151
  # private
151
152
 
152
- def fetch_addresses(self, component: str, wait=None) -> dict[str, ServiceAddress]:
153
- addresses : dict[str, ServiceAddress] = {} # channel name -> ServiceAddress
153
+ def fetch_addresses(self, component: str, wait=None) -> dict[str, ChannelInstances]:
154
+ addresses : dict[str, ChannelInstances] = {} # channel name -> ServiceAddress
154
155
 
155
156
  index, nodes = self.consul.health.service(component, passing=True, index=self.last_index.get(component, None), wait=wait)
156
157
  self.last_index[component] = index
@@ -170,7 +171,7 @@ class ConsulComponentRegistry(ComponentRegistry):
170
171
 
171
172
  address = addresses.get(channel, None)
172
173
  if address is None:
173
- address = ServiceAddress(component=component, channel=channel_name)
174
+ address = ChannelInstances(component=component, channel=channel_name)
174
175
  addresses[channel] = address
175
176
 
176
177
  address.urls.append(url)
@@ -200,7 +201,7 @@ class ConsulComponentRegistry(ComponentRegistry):
200
201
 
201
202
  self.register_service(name, id, descriptor.health, tags =["component"], meta={"channels": addresses})
202
203
 
203
- def get_addresses(self, descriptor: ComponentDescriptor) -> list[ServiceAddress]:
204
+ def get_addresses(self, descriptor: ComponentDescriptor) -> list[ChannelInstances]:
204
205
  component_addresses = self.component_addresses.get(descriptor.name, None)
205
206
  if component_addresses is None:
206
207
  component_addresses = self.fetch_addresses(descriptor.name)
@@ -1,3 +1,6 @@
1
+ """
2
+ rest channel implementation
3
+ """
1
4
  import inspect
2
5
  import re
3
6
  from dataclasses import is_dataclass, asdict
@@ -7,7 +10,7 @@ from typing import get_type_hints, TypeVar, Annotated, Callable, get_origin, get
7
10
  from pydantic import BaseModel
8
11
 
9
12
  from aspyx.reflection import DynamicProxy, Decorators
10
- from .service import RemoteServiceException, ServiceException, channel
13
+ from .service import ServiceException, channel
11
14
 
12
15
  T = TypeVar("T")
13
16
 
@@ -25,14 +28,26 @@ QueryParam = lambda t: Annotated[t, QueryParamMarker]
25
28
 
26
29
  # decorators
27
30
 
28
- def rest(url):
31
+ def rest(url=""):
32
+ """
33
+ mark service interfaces to add a url prefix
34
+
35
+ Args:
36
+ url: prefix that will be added to all urls
37
+ """
29
38
  def decorator(cls):
30
39
  Decorators.add(cls, rest, url)
31
40
 
32
41
  return cls
33
42
  return decorator
34
43
 
35
- def get(url):
44
+ def get(url: str):
45
+ """
46
+ methods marked with `get` will be executed by calling a http get request.
47
+
48
+ Args:
49
+ url: the url
50
+ """
36
51
  def decorator(cls):
37
52
  Decorators.add(cls, get, url)
38
53
 
@@ -40,7 +55,14 @@ def get(url):
40
55
  return decorator
41
56
 
42
57
 
43
- def post(url):
58
+ def post(url: str):
59
+ """
60
+ methods marked with `post` will be executed by calling a http get request.
61
+ The body parameter should be marked with `Body(<param>)`
62
+
63
+ Args:
64
+ url: the url
65
+ """
44
66
  def decorator(cls):
45
67
  Decorators.add(cls, post, url)
46
68
 
@@ -48,7 +70,13 @@ def post(url):
48
70
 
49
71
  return decorator
50
72
 
51
- def put(url):
73
+ def put(url: str):
74
+ """
75
+ methods marked with `put` will be executed by calling a http put request.
76
+
77
+ Args:
78
+ url: the url
79
+ """
52
80
  def decorator(cls):
53
81
  Decorators.add(cls, put, url)
54
82
 
@@ -56,7 +84,13 @@ def put(url):
56
84
 
57
85
  return decorator
58
86
 
59
- def delete(url):
87
+ def delete(url: str):
88
+ """
89
+ methods marked with `delete` will be executed by calling a http delete request.
90
+
91
+ Args:
92
+ url: the url
93
+ """
60
94
  def decorator(cls):
61
95
  Decorators.add(cls, delete, url)
62
96
 
@@ -64,7 +98,13 @@ def delete(url):
64
98
 
65
99
  return decorator
66
100
 
67
- def patch(url):
101
+ def patch(url: str):
102
+ """
103
+ methods marked with `patch` will be executed by calling a http patch request.
104
+
105
+ Args:
106
+ url: the url
107
+ """
68
108
  def decorator(cls):
69
109
  Decorators.add(cls, patch, url)
70
110
 
@@ -72,20 +112,22 @@ def patch(url):
72
112
 
73
113
  return decorator
74
114
 
75
-
76
-
77
-
78
115
  @channel("rest")
79
116
  class RestChannel(HTTPXChannel):
117
+ """
118
+ A rest channel executes http requests as specified by the corresponding decorators and annotations,
119
+ """
80
120
  __slots__ = [
81
121
  "signature",
82
122
  "url_template",
83
123
  "type",
124
+ "calls",
84
125
  "return_type",
85
126
  "path_param_names",
86
127
  "query_param_names",
87
128
  "body_param_name"
88
129
  ]
130
+
89
131
  # local class
90
132
 
91
133
  class Call:
@@ -133,7 +175,7 @@ class RestChannel(HTTPXChannel):
133
175
  # constructor
134
176
 
135
177
  def __init__(self):
136
- super().__init__("rest")
178
+ super().__init__()
137
179
 
138
180
  self.calls : dict[Callable, RestChannel.Call] = {}
139
181
 
@@ -147,19 +189,6 @@ class RestChannel(HTTPXChannel):
147
189
 
148
190
  return call
149
191
 
150
- def to_dict(self, obj):
151
- if obj is None:
152
- return None
153
- if is_dataclass(obj):
154
- return asdict(obj)
155
- elif isinstance(obj, BaseModel):
156
- return obj.dict()
157
- elif hasattr(obj, "__dict__"):
158
- return vars(obj)
159
- else:
160
- # fallback for primitives etc.
161
- return obj
162
-
163
192
  # override
164
193
 
165
194
  def invoke(self, invocation: DynamicProxy.Invocation):
@@ -196,4 +225,4 @@ class RestChannel(HTTPXChannel):
196
225
  raise ServiceException(
197
226
  f"no url for channel {self.name} for component {self.component_descriptor.name} registered")
198
227
  except Exception as e:
199
- raise ServiceException(f"communication exception {e}") from e
228
+ raise ServiceException(f"communication exception {e}") from e
@@ -98,7 +98,7 @@ class TypeDeserializer:
98
98
  return deser_union
99
99
 
100
100
  if isinstance(typ, type) and issubclass(typ, BaseModel):
101
- return lambda v: typ.parse_obj(v)
101
+ return typ.parse_obj
102
102
 
103
103
  if is_dataclass(typ):
104
104
  field_deserializers = {f.name: TypeDeserializer(f.type) for f in fields(typ)}
@@ -122,4 +122,13 @@ class TypeDeserializer:
122
122
 
123
123
  @lru_cache(maxsize=512)
124
124
  def get_deserializer(typ):
125
+ """
126
+ return a function that is able to deserialize a value of the specified type
127
+
128
+ Args:
129
+ typ: the type
130
+
131
+ Returns:
132
+
133
+ """
125
134
  return TypeDeserializer(typ)
aspyx_service/server.py CHANGED
@@ -2,7 +2,6 @@
2
2
  FastAPI server implementation for the aspyx service framework.
3
3
  """
4
4
  import atexit
5
- import functools
6
5
  import inspect
7
6
  import threading
8
7
  from typing import Type, Optional, Callable
@@ -28,6 +27,9 @@ from .restchannel import get, post, put, delete, rest
28
27
 
29
28
 
30
29
  class FastAPIServer(Server):
30
+ """
31
+ A server utilizing fastapi framework.
32
+ """
31
33
  # constructor
32
34
 
33
35
  def __init__(self, host="0.0.0.0", port=8000, **kwargs):
@@ -41,7 +43,7 @@ class FastAPIServer(Server):
41
43
  self.component_registry: Optional[ComponentRegistry] = None
42
44
 
43
45
  self.router = APIRouter()
44
- self.fast_api = FastAPI(host=self.host, port=Server.port, debug=True)
46
+ self.fast_api = FastAPI()
45
47
 
46
48
  # cache
47
49
 
@@ -82,14 +84,21 @@ class FastAPIServer(Server):
82
84
  )
83
85
 
84
86
  def start(self):
85
- def run():
86
- uvicorn.run(self.fast_api, host=self.host, port=self.port, access_log=False)
87
+ """
88
+ start the fastapi server in a thread
89
+ """
90
+
91
+ config = uvicorn.Config(self.fast_api, host=self.host, port=self.port, log_level="debug")
92
+ server = uvicorn.Server(config)
87
93
 
88
- self.server_thread = threading.Thread(target=run, daemon=True)
89
- self.server_thread.start()
94
+ thread = threading.Thread(target=server.run, daemon=True)
95
+ thread.start()
90
96
 
91
97
  print(f"server started on {self.host}:{self.port}")
92
98
 
99
+ return thread
100
+
101
+
93
102
  def get_deserializers(self, service: Type, method):
94
103
  deserializers = self.deserializers.get(method, None)
95
104
  if deserializers is None:
@@ -105,8 +114,8 @@ class FastAPIServer(Server):
105
114
 
106
115
  deserializers = self.get_deserializers(type, method)
107
116
 
108
- for i in range(0, len(args)):
109
- args[i] = deserializers[i](args[i])
117
+ for i, arg in enumerate(args):
118
+ args[i] = deserializers[i](arg)
110
119
 
111
120
  return args
112
121
 
@@ -181,6 +190,15 @@ class FastAPIServer(Server):
181
190
  self.router.get(url)(get_health_response)
182
191
 
183
192
  def boot(self, module_type: Type) -> Environment:
193
+ """
194
+ startup the service manager, DI framework and the fastapi server based on the supplied module
195
+
196
+ Args:
197
+ module_type: the module
198
+
199
+ Returns:
200
+
201
+ """
184
202
  # setup environment
185
203
 
186
204
  self.environment = Environment(module_type)
@@ -194,8 +212,8 @@ class FastAPIServer(Server):
194
212
  self.add_routes()
195
213
  self.fast_api.include_router(self.router)
196
214
 
197
- #for route in self.fast_api.routes:
198
- # print(f"{route.name}: {route.path} [{route.methods}]")
215
+ for route in self.fast_api.routes:
216
+ print(f"{route.name}: {route.path} [{route.methods}]")
199
217
 
200
218
  # start server thread
201
219