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