aspyx-service 0.10.2__py3-none-any.whl → 0.10.4__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,12 +4,14 @@ 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, ChannelInstances, ServiceManager, Component, Service, AbstractComponent, ComponentStatus, ComponentRegistry, implementation, health, component, service
8
- from .channels import HTTPXChannel, DispatchJSONChannel
7
+ from .service import AuthorizationException, MissingTokenException, RemoteServiceException, ServiceCommunicationException, TokenException, TokenExpiredException, InvalidTokenException, component_services, ServiceException, Server, Channel, ComponentDescriptor, inject_service, ChannelAddress, ChannelInstances, ServiceManager, Component, Service, AbstractComponent, ComponentStatus, ComponentRegistry, implementation, health, component, service
8
+ from .channels import HTTPXChannel, DispatchJSONChannel, TokenContext
9
9
  from .registries import ConsulComponentRegistry
10
- from .server import FastAPIServer
10
+ from .server import FastAPIServer, RequestContext
11
11
  from .healthcheck import health_checks, health_check, HealthCheckManager, HealthStatus
12
12
  from .restchannel import RestChannel, post, get, put, delete, QueryParam, Body, rest
13
+ from .session import Session, SessionManager
14
+ from .authorization import AuthorizationManager, AbstractAuthorizationFactory, AuthorizationException
13
15
 
14
16
 
15
17
  @module()
@@ -38,6 +40,25 @@ __all__ = [
38
40
  "service",
39
41
  "implementation",
40
42
  "inject_service",
43
+ "component_services",
44
+ "RemoteServiceException",
45
+ "ServiceCommunicationException",
46
+ "TokenException",
47
+ "TokenExpiredException",
48
+ "InvalidTokenException",
49
+ "MissingTokenException",
50
+ "AuthorizationException",
51
+
52
+ # authorization
53
+
54
+ "AuthorizationManager",
55
+ "AbstractAuthorizationFactory",
56
+ "AuthorizationException",
57
+
58
+ # session
59
+
60
+ "Session",
61
+ "SessionManager",
41
62
 
42
63
  # healthcheck
43
64
 
@@ -54,6 +75,7 @@ __all__ = [
54
75
 
55
76
  "HTTPXChannel",
56
77
  "DispatchJSONChannel",
78
+ "TokenContext",
57
79
 
58
80
  # rest
59
81
 
@@ -69,8 +91,10 @@ __all__ = [
69
91
  # registries
70
92
 
71
93
  "ConsulComponentRegistry",
94
+ "RequestContext",
72
95
 
73
96
  # server
74
97
 
75
- "FastAPIServer"
98
+ "FastAPIServer",
99
+ "RequestContext"
76
100
  ]
@@ -0,0 +1,135 @@
1
+ """
2
+ authorization logic
3
+ """
4
+ import inspect
5
+ from abc import abstractmethod, ABC
6
+ from typing import Optional, Callable
7
+
8
+ from aspyx.di import injectable, inject, order
9
+ from aspyx.di.aop import Invocation
10
+ from aspyx.reflection import TypeDescriptor, Decorators
11
+
12
+
13
+ class AuthorizationException(Exception):
14
+ """
15
+ Any authorization exception
16
+ """
17
+ pass
18
+
19
+ def get_method_class(method):
20
+ if inspect.ismethod(method) or inspect.isfunction(method):
21
+ qualname = method.__qualname__
22
+ parts = qualname.split('.')
23
+ if len(parts) > 1:
24
+ cls_name = parts[-2]
25
+ module = inspect.getmodule(method)
26
+ if module:
27
+ for name, obj in inspect.getmembers(module, inspect.isclass):
28
+ if name == cls_name and hasattr(obj, method.__name__):
29
+ return obj
30
+ return None
31
+
32
+ @injectable()
33
+ class AuthorizationManager:
34
+ """
35
+ The authorization manager is used to remember and execute pluggable authorization checks.
36
+ """
37
+ class Authorization():
38
+ """
39
+ Base class for authorization checks
40
+ """
41
+ def check(self, invocation: Invocation):
42
+ """
43
+ execute the authorization check. Throws an exception in case of violations,
44
+ """
45
+ pass
46
+
47
+ class AuthorizationFactory(ABC):
48
+ """
49
+ An authorization factory is used to create possible authorization checks given a method descriptor
50
+ """
51
+
52
+ def __init__(self, order = 0):
53
+ self.order = order
54
+
55
+ @abstractmethod
56
+ def compute_authorization(self, method_descriptor: TypeDescriptor.MethodDescriptor) -> Optional['AuthorizationManager.Authorization']:
57
+ """
58
+ return a possible authorization check given a method descriptor
59
+ Args:
60
+ method_descriptor: the corresponding method descriptor
61
+
62
+ Returns:
63
+ an authorization check or None
64
+ """
65
+ pass
66
+
67
+ # constructor
68
+
69
+ def __init__(self):
70
+ self.factories : list[AuthorizationManager.AuthorizationFactory] = []
71
+ self.checks : dict[Callable, list[AuthorizationManager.Authorization]] = {}
72
+
73
+ # public
74
+
75
+ def register_factory(self, factory: 'AuthorizationManager.AuthorizationFactory'):
76
+ self.factories.append(factory)
77
+
78
+ self.factories.sort(key=lambda factory: factory.order)
79
+
80
+ # internal
81
+
82
+ def compute_checks(self, func: Callable) -> list[Authorization]:
83
+ checks = []
84
+
85
+ clazz = get_method_class(func)
86
+
87
+ descriptor = TypeDescriptor.for_type(clazz).get_method(func.__name__)
88
+
89
+ for factory in self.factories:
90
+ check = factory.compute_authorization(descriptor)
91
+ if check is not None:
92
+ checks.append(check)
93
+
94
+ return checks
95
+
96
+ def get_checks(self, func: Callable) -> list[Authorization]:
97
+ """
98
+ return a list of authorization checks given a function.
99
+
100
+ Args:
101
+ func: the corresponding function.
102
+
103
+ Returns:
104
+ list of authorization checks
105
+ """
106
+ checks = self.checks.get(func, None)
107
+ if checks is None:
108
+ checks = self.compute_checks(func)
109
+ self.checks[func] = checks
110
+ print(checks)
111
+
112
+ return checks
113
+
114
+ def check(self, invocation: Invocation) -> Optional[Authorization]:
115
+ for check in self.get_checks(invocation.func):
116
+ check.check(invocation)
117
+
118
+ class AbstractAuthorizationFactory(AuthorizationManager.AuthorizationFactory):
119
+ """
120
+ Abstract base class for authorization factories
121
+ """
122
+
123
+ # constructor
124
+
125
+ def __init__(self):
126
+ super().__init__(0)
127
+
128
+ if Decorators.has_decorator(type(self), order):
129
+ self.order = Decorators.get_decorator(type(self), order).args[0]
130
+
131
+ # inject
132
+
133
+ @inject()
134
+ def set_authorization_manager(self, authorization_manager: AuthorizationManager):
135
+ authorization_manager.register_factory(self)
aspyx_service/channels.py CHANGED
@@ -3,21 +3,62 @@ 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
+ import typing
7
+ from contextlib import contextmanager
8
+ from dataclasses import is_dataclass, fields
8
9
  from typing import Type, Optional, Any, Callable
9
10
 
11
+ import httpx
10
12
  import msgpack
11
- from httpx import Client, AsyncClient
13
+ from httpx import Client, AsyncClient, USE_CLIENT_DEFAULT
12
14
  from pydantic import BaseModel
13
15
 
14
16
  from aspyx.di.configuration import inject_value
15
17
  from aspyx.reflection import DynamicProxy, TypeDescriptor
16
- from .service import ServiceManager, ServiceCommunicationException
18
+ from aspyx.threading import ThreadLocal, ContextLocal
19
+ from .service import ServiceManager, ServiceCommunicationException, TokenExpiredException, InvalidTokenException, \
20
+ AuthorizationException, MissingTokenException
17
21
 
18
22
  from .service import ComponentDescriptor, ChannelInstances, ServiceException, channel, Channel, RemoteServiceException
19
- from .serialization import get_deserializer
23
+ from .serialization import get_deserializer, TypeDeserializer, TypeSerializer, get_serializer
20
24
 
25
+ class TokenContext:
26
+ """
27
+ TokeContext covers two context locals for both the access and - optional - refresh topen
28
+ """
29
+ access_token = ContextLocal[str]("access_token", default=None)
30
+ refresh_token = ContextLocal[str]("refresh_token", default=None)
31
+
32
+ @classmethod
33
+ def get_access_token(cls) -> Optional[str]:
34
+ return cls.access_token.get()
35
+
36
+ @classmethod
37
+ def get_refresh_token(cls) -> Optional[str]:
38
+ return cls.refresh_token.get()
39
+
40
+
41
+ @classmethod
42
+ def set(cls, access_token: str, refresh_token: Optional[str] = None):
43
+ cls.access_token.set(access_token)
44
+ if refresh_token:
45
+ cls.refresh_token.set(refresh_token)
46
+
47
+ @classmethod
48
+ def clear(cls):
49
+ cls.access_token.set(None)
50
+ cls.refresh_token.set(None)
51
+
52
+ @classmethod
53
+ @contextmanager
54
+ def use(cls, access_token: str, refresh_token: Optional[str] = None):
55
+ access_token = cls.access_token.set(access_token)
56
+ refresh_token = cls.refresh_token.set(refresh_token)
57
+ try:
58
+ yield
59
+ finally:
60
+ cls.access_token.reset(access_token)
61
+ cls.refresh_token.reset(refresh_token)
21
62
 
22
63
  class HTTPXChannel(Channel):
23
64
  __slots__ = [
@@ -25,16 +66,21 @@ class HTTPXChannel(Channel):
25
66
  "async_client",
26
67
  "service_names",
27
68
  "deserializers",
28
- "timeout"
69
+ "timeout",
70
+ "optimize_serialization"
29
71
  ]
30
72
 
73
+ # class properties
74
+
75
+ client_local = ThreadLocal[Client]()
76
+ async_client_local = ThreadLocal[AsyncClient]()
77
+
31
78
  # class methods
32
79
 
33
80
  @classmethod
34
81
  def to_dict(cls, obj: Any) -> Any:
35
82
  if isinstance(obj, BaseModel):
36
- return obj.dict()
37
-
83
+ return obj.model_dump()
38
84
 
39
85
  elif is_dataclass(obj):
40
86
  return {
@@ -51,20 +97,16 @@ class HTTPXChannel(Channel):
51
97
 
52
98
  return obj
53
99
 
54
- @classmethod
55
- def to_json(cls, obj) -> str:
56
- return json.dumps(cls.to_dict(obj))
57
-
58
100
  # constructor
59
101
 
60
102
  def __init__(self):
61
103
  super().__init__()
62
104
 
63
105
  self.timeout = 1000.0
64
- self.client: Optional[Client] = None
65
- self.async_client: Optional[AsyncClient] = None
66
106
  self.service_names: dict[Type, str] = {}
107
+ self.serializers: dict[Callable, list[Callable]] = {}
67
108
  self.deserializers: dict[Callable, Callable] = {}
109
+ self.optimize_serialization = True
68
110
 
69
111
  # inject
70
112
 
@@ -74,7 +116,27 @@ class HTTPXChannel(Channel):
74
116
 
75
117
  # protected
76
118
 
77
- def get_deserializer(self, type: Type, method: Callable) -> Type:
119
+ def serialize_args(self, invocation: DynamicProxy.Invocation) -> list[Any]:
120
+ deserializers = self.get_serializers(invocation.type, invocation.method)
121
+
122
+ args = list(invocation.args)
123
+ for index, deserializer in enumerate(deserializers):
124
+ args[index] = deserializer(args[index])
125
+
126
+ return args
127
+
128
+ def get_serializers(self, type: Type, method: Callable) -> list[TypeSerializer]:
129
+ serializers = self.serializers.get(method, None)
130
+ if serializers is None:
131
+ param_types = TypeDescriptor.for_type(type).get_method(method.__name__).param_types
132
+
133
+ serializers = [get_serializer(type) for type in param_types]
134
+
135
+ self.serializers[method] = serializers
136
+
137
+ return serializers
138
+
139
+ def get_deserializer(self, type: Type, method: Callable) -> TypeDeserializer:
78
140
  deserializer = self.deserializers.get(method, None)
79
141
  if deserializer is None:
80
142
  return_type = TypeDescriptor.for_type(type).get_method(method.__name__).return_type
@@ -95,24 +157,25 @@ class HTTPXChannel(Channel):
95
157
  for service in component_descriptor.services:
96
158
  self.service_names[service.type] = service.name
97
159
 
98
- # make client
99
-
100
- self.client = self.make_client()
101
- self.async_client = self.make_async_client()
102
-
103
160
  # public
104
161
 
105
162
  def get_client(self) -> Client:
106
- if self.client is None:
107
- self.client = self.make_client()
163
+ client = self.client_local.get()
164
+
165
+ if client is None:
166
+ client = self.make_client()
167
+ self.client_local.set(client)
108
168
 
109
- return self.client
169
+ return client
110
170
 
111
171
  def get_async_client(self) -> AsyncClient:
112
- if self.async_client is None:
113
- self.async_client = self.make_async_client()
172
+ async_client = self.async_client_local.get()
114
173
 
115
- return self.async_client
174
+ if async_client is None:
175
+ async_client = self.make_async_client()
176
+ self.async_client_local.set(async_client)
177
+
178
+ return async_client
116
179
 
117
180
  def make_client(self) -> Client:
118
181
  return Client() # base_url=url
@@ -120,6 +183,79 @@ class HTTPXChannel(Channel):
120
183
  def make_async_client(self) -> AsyncClient:
121
184
  return AsyncClient() # base_url=url
122
185
 
186
+ def request(self, http_method: str, url: str, json: Optional[typing.Any] = None,
187
+ params: Optional[Any] = None, headers: Optional[Any] = None,
188
+ timeout: Any = USE_CLIENT_DEFAULT, content: Optional[Any] = None) -> httpx.Response:
189
+
190
+ token = TokenContext.get_access_token()
191
+ if token is not None:
192
+ if headers is None: # None is also valid!
193
+ headers = {}
194
+
195
+ ## add bearer token
196
+
197
+ headers["Authorization"] = f"Bearer {token}"
198
+
199
+ try:
200
+ response = self.get_client().request(http_method, url, params=params, json=json, headers=headers, timeout=timeout, content=content)
201
+ response.raise_for_status()
202
+ except httpx.RequestError as e:
203
+ raise ServiceCommunicationException(str(e)) from e
204
+
205
+ except httpx.HTTPStatusError as e:
206
+ if e.response.status_code == 401:
207
+ www_auth = e.response.headers.get("www-authenticate", "")
208
+ if "invalid_token" in www_auth:
209
+ if 'expired' in www_auth:
210
+ raise TokenExpiredException() from e
211
+ elif 'missing' in www_auth:
212
+ raise MissingTokenException() from e
213
+ else:
214
+ raise InvalidTokenException() from e
215
+
216
+ raise AuthorizationException(str(e)) from e
217
+ except httpx.HTTPError as e:
218
+ raise RemoteServiceException(str(e)) from e
219
+
220
+ return response
221
+
222
+ async def request_async(self, http_method: str, url: str, json: Optional[typing.Any] = None,
223
+ params: Optional[Any] = None, headers: Optional[Any] = None,
224
+ timeout: Any = USE_CLIENT_DEFAULT, content: Optional[Any] = None) -> httpx.Response:
225
+
226
+ token = TokenContext.get_access_token()
227
+ if token is not None:
228
+ if headers is None: # None is also valid!
229
+ headers = {}
230
+
231
+ ## add bearer token
232
+
233
+ headers["Authorization"] = f"Bearer {token}"
234
+
235
+ try:
236
+ response = await self.get_async_client().request(http_method, url, params=params, json=json, headers=headers,
237
+ timeout=timeout, content=content)
238
+ response.raise_for_status()
239
+ except httpx.RequestError as e:
240
+ raise ServiceCommunicationException(str(e)) from e
241
+
242
+ except httpx.HTTPStatusError as e:
243
+ if e.response.status_code == 401:
244
+ www_auth = e.response.headers.get("www-authenticate", "")
245
+ if "invalid_token" in www_auth:
246
+ if 'expired' in www_auth:
247
+ raise TokenExpiredException() from e
248
+ elif 'missing' in www_auth:
249
+ raise MissingTokenException() from e
250
+ else:
251
+ raise InvalidTokenException() from e
252
+
253
+ raise RemoteServiceException(str(e)) from e
254
+ except httpx.HTTPError as e:
255
+ raise RemoteServiceException(str(e)) from e
256
+
257
+ return response
258
+
123
259
  class Request(BaseModel):
124
260
  method: str # component:service:method
125
261
  args: tuple[Any, ...]
@@ -153,20 +289,25 @@ class DispatchJSONChannel(HTTPXChannel):
153
289
 
154
290
  def invoke(self, invocation: DynamicProxy.Invocation):
155
291
  service_name = self.service_names[invocation.type] # map type to registered service name
156
- request = Request(method=f"{self.component_descriptor.name}:{service_name}:{invocation.method.__name__}",
157
- args=invocation.args)
158
292
 
159
- dict = self.to_dict(request)
160
- try:
161
- result = Response(**self.get_client().post(f"{self.get_url()}/invoke", json=dict, timeout=self.timeout).json())
162
- if result.exception is not None:
163
- raise RemoteServiceException(f"server side exception {result.exception}")
293
+ request : dict = {
294
+ "method": f"{self.component_descriptor.name}:{service_name}:{invocation.method.__name__}"
295
+ #"args": invocation.args
296
+ }
164
297
 
165
- return self.get_deserializer(invocation.type, invocation.method)(result.result)
166
- except ServiceCommunicationException:
167
- raise
298
+ if self.optimize_serialization:
299
+ request["args"] = self.serialize_args(invocation)
300
+ else:
301
+ request["args"] = self.to_dict(invocation.args)
168
302
 
169
- except RemoteServiceException:
303
+ try:
304
+ http_result = self.request( "post", f"{self.get_url()}/invoke", json=request, timeout=self.timeout)
305
+ result = http_result.json()
306
+ if result["exception"] is not None:
307
+ raise RemoteServiceException(f"server side exception {result['exception']}")
308
+
309
+ return self.get_deserializer(invocation.type, invocation.method)(result["result"])
310
+ except (ServiceCommunicationException, AuthorizationException, RemoteServiceException) as e:
170
311
  raise
171
312
 
172
313
  except Exception as e:
@@ -175,22 +316,25 @@ class DispatchJSONChannel(HTTPXChannel):
175
316
 
176
317
  async def invoke_async(self, invocation: DynamicProxy.Invocation):
177
318
  service_name = self.service_names[invocation.type] # map type to registered service name
178
- request = Request(method=f"{self.component_descriptor.name}:{service_name}:{invocation.method.__name__}",
179
- args=invocation.args)
180
- dict = self.to_dict(request)
181
- try:
182
- data = await self.get_async_client().post(f"{self.get_url()}/invoke", json=dict, timeout=self.timeout)
183
- result = Response(**data.json())
319
+ request : dict = {
320
+ "method": f"{self.component_descriptor.name}:{service_name}:{invocation.method.__name__}"
321
+ }
184
322
 
185
- if result.exception is not None:
186
- raise RemoteServiceException(f"server side exception {result.exception}")
323
+ if self.optimize_serialization:
324
+ request["args"] = self.serialize_args(invocation)
325
+ else:
326
+ request["args"] = self.to_dict(invocation.args)
187
327
 
188
- return self.get_deserializer(invocation.type, invocation.method)(result.result)
328
+ try:
329
+ data = await self.request_async("post", f"{self.get_url()}/invoke", json=request, timeout=self.timeout)
330
+ result = data.json()
189
331
 
190
- except ServiceCommunicationException:
191
- raise
332
+ if result["exception"] is not None:
333
+ raise RemoteServiceException(f"server side exception {result['exception']}")
192
334
 
193
- except RemoteServiceException:
335
+ return self.get_deserializer(invocation.type, invocation.method)(result["result"])
336
+
337
+ except (ServiceCommunicationException, AuthorizationException, RemoteServiceException) as e:
194
338
  raise
195
339
 
196
340
  except Exception as e:
@@ -216,13 +360,19 @@ class DispatchMSPackChannel(HTTPXChannel):
216
360
 
217
361
  def invoke(self, invocation: DynamicProxy.Invocation):
218
362
  service_name = self.service_names[invocation.type] # map type to registered service name
219
- request = Request(method=f"{self.component_descriptor.name}:{service_name}:{invocation.method.__name__}",
220
- args=invocation.args)
363
+ request: dict = {
364
+ "method": f"{self.component_descriptor.name}:{service_name}:{invocation.method.__name__}"
365
+ }
366
+
367
+ if self.optimize_serialization:
368
+ request["args"] = self.serialize_args(invocation)
369
+ else:
370
+ request["args"] = self.to_dict(invocation.args)
221
371
 
222
372
  try:
223
- packed = msgpack.packb(self.to_dict(request), use_bin_type=True)
373
+ packed = msgpack.packb(request, use_bin_type=True)
224
374
 
225
- response = self.get_client().post(
375
+ response = self.request("post",
226
376
  f"{self.get_url()}/invoke",
227
377
  content=packed,
228
378
  headers={"Content-Type": "application/msgpack"},
@@ -236,6 +386,25 @@ class DispatchMSPackChannel(HTTPXChannel):
236
386
 
237
387
  return self.get_deserializer(invocation.type, invocation.method)(result["result"])
238
388
 
389
+
390
+ except httpx.RequestError as e:
391
+ raise ServiceCommunicationException(str(e)) from e
392
+
393
+ except httpx.HTTPStatusError as e:
394
+ if e.response.status_code == 401:
395
+ www_auth = e.response.headers.get("www-authenticate", "")
396
+ if "invalid_token" in www_auth:
397
+ if 'expired' in www_auth:
398
+ raise TokenExpiredException() from e
399
+ elif 'missing' in www_auth:
400
+ raise MissingTokenException() from e
401
+ else:
402
+ raise InvalidTokenException() from e
403
+
404
+ raise RemoteServiceException(str(e)) from e
405
+ except httpx.HTTPError as e:
406
+ raise RemoteServiceException(str(e)) from e
407
+
239
408
  except ServiceCommunicationException:
240
409
  raise
241
410
 
@@ -247,13 +416,19 @@ class DispatchMSPackChannel(HTTPXChannel):
247
416
 
248
417
  async def invoke_async(self, invocation: DynamicProxy.Invocation):
249
418
  service_name = self.service_names[invocation.type] # map type to registered service name
250
- request = Request(method=f"{self.component_descriptor.name}:{service_name}:{invocation.method.__name__}",
251
- args=invocation.args)
419
+ request: dict = {
420
+ "method": f"{self.component_descriptor.name}:{service_name}:{invocation.method.__name__}"
421
+ }
422
+
423
+ if self.optimize_serialization:
424
+ request["args"] = self.serialize_args(invocation)
425
+ else:
426
+ request["args"] = self.to_dict(invocation.args)
252
427
 
253
428
  try:
254
- packed = msgpack.packb(self.to_dict(request), use_bin_type=True)
429
+ packed = msgpack.packb(request, use_bin_type=True)
255
430
 
256
- response = await self.get_async_client().post(
431
+ response = await self.request_async("post",
257
432
  f"{self.get_url()}/invoke",
258
433
  content=packed,
259
434
  headers={"Content-Type": "application/msgpack"},
@@ -267,6 +442,25 @@ class DispatchMSPackChannel(HTTPXChannel):
267
442
 
268
443
  return self.get_deserializer(invocation.type, invocation.method)(result["result"])
269
444
 
445
+ except httpx.RequestError as e:
446
+ raise ServiceCommunicationException(str(e)) from e
447
+
448
+ except httpx.HTTPStatusError as e:
449
+ if e.response.status_code == 401:
450
+ www_auth = e.response.headers.get("www-authenticate", "")
451
+ if "invalid_token" in www_auth:
452
+ if 'expired' in www_auth:
453
+ raise TokenExpiredException() from e
454
+ elif 'missing' in www_auth:
455
+ raise MissingTokenException() from e
456
+ else:
457
+ raise InvalidTokenException() from e
458
+
459
+ raise RemoteServiceException(str(e)) from e
460
+
461
+ except httpx.HTTPError as e:
462
+ raise RemoteServiceException(str(e)) from e
463
+
270
464
  except ServiceCommunicationException:
271
465
  raise
272
466
 
@@ -117,7 +117,7 @@ class HealthCheckManager:
117
117
  "status": str(self.status),
118
118
  }
119
119
 
120
- if len(self.details) > 0:
120
+ if self.details:
121
121
  result["details"] = self.details
122
122
 
123
123
  return result
@@ -42,19 +42,19 @@ class ConsulComponentRegistry(ComponentRegistry):
42
42
  # injections
43
43
 
44
44
  @inject_value("consul.watchdog.interval", default=5)
45
- def set_interval(self, interval):
45
+ def set_watchdog_interval(self, interval):
46
46
  self.watchdog_interval = interval
47
47
 
48
48
  @inject_value("consul.healthcheck.interval", default="10s")
49
- def set_interval(self, interval):
49
+ def set_healthcheck_interval(self, interval):
50
50
  self.healthcheck_interval = interval
51
51
 
52
52
  @inject_value("consul.healthcheck.timeout", default="3s")
53
- def set_interval(self, interval):
53
+ def set_healthcheck_timeout(self, interval):
54
54
  self.healthcheck_timeout = interval
55
55
 
56
56
  @inject_value("consul.healthcheck.deregister", default="5m")
57
- def set_interval(self, interval):
57
+ def set_healthcheck_deregister(self, interval):
58
58
  self.healthcheck_deregister = interval
59
59
 
60
60
  # lifecycle hooks
@@ -220,7 +220,7 @@ class ConsulComponentRegistry(ComponentRegistry):
220
220
 
221
221
  # only cache if non-empty
222
222
 
223
- if len(component_addresses) > 0:
223
+ if component_addresses:
224
224
  self.component_addresses[descriptor.name] = component_addresses
225
225
 
226
226
  return list(component_addresses.values())