aspyx-service 0.11.2__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.
- aspyx_service/__init__.py +106 -0
- aspyx_service/authorization.py +126 -0
- aspyx_service/channels.py +445 -0
- aspyx_service/generator/__init__.py +16 -0
- aspyx_service/generator/json_schema_generator.py +197 -0
- aspyx_service/generator/openapi_generator.py +120 -0
- aspyx_service/healthcheck.py +194 -0
- aspyx_service/protobuf.py +1093 -0
- aspyx_service/registries.py +241 -0
- aspyx_service/restchannel.py +313 -0
- aspyx_service/server.py +576 -0
- aspyx_service/service.py +968 -0
- aspyx_service/session.py +136 -0
- aspyx_service-0.11.2.dist-info/METADATA +555 -0
- aspyx_service-0.11.2.dist-info/RECORD +17 -0
- aspyx_service-0.11.2.dist-info/WHEEL +4 -0
- aspyx_service-0.11.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
from typing import get_type_hints, get_origin, get_args, Any
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
from aspyx.reflection import TypeDescriptor
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class JSONSchemaGenerator:
|
|
10
|
+
"""
|
|
11
|
+
Generates a JSON Schema-based service descriptor for framework-agnostic
|
|
12
|
+
service discovery and handshaking between systems.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
PRIMITIVES = {
|
|
16
|
+
str: {"type": "string"},
|
|
17
|
+
int: {"type": "integer"},
|
|
18
|
+
float: {"type": "number"},
|
|
19
|
+
bool: {"type": "boolean"},
|
|
20
|
+
type(None): {"type": "null"},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def __init__(self, service_manager):
|
|
24
|
+
self.service_manager = service_manager
|
|
25
|
+
self.type_defs: dict[str, dict] = {} # Stores reusable type definitions
|
|
26
|
+
self.processed_types: set[type] = set() # Track processed types to avoid duplicates
|
|
27
|
+
|
|
28
|
+
def _get_type_name(self, typ: type) -> str:
|
|
29
|
+
"""Get a unique name for a type."""
|
|
30
|
+
if hasattr(typ, '__name__'):
|
|
31
|
+
return typ.__name__
|
|
32
|
+
return str(typ)
|
|
33
|
+
|
|
34
|
+
def _process_type(self, typ: type) -> dict:
|
|
35
|
+
"""
|
|
36
|
+
Process a type and return its JSON Schema representation.
|
|
37
|
+
Complex types are added to type_defs and referenced via $ref.
|
|
38
|
+
"""
|
|
39
|
+
# Handle None type
|
|
40
|
+
if typ is type(None):
|
|
41
|
+
return {"type": "null"}
|
|
42
|
+
|
|
43
|
+
# Handle primitives
|
|
44
|
+
if typ in self.PRIMITIVES:
|
|
45
|
+
return self.PRIMITIVES[typ].copy()
|
|
46
|
+
|
|
47
|
+
# Handle Optional types (Union[X, None])
|
|
48
|
+
origin = get_origin(typ)
|
|
49
|
+
if origin is type(None) or origin is type(None).__class__:
|
|
50
|
+
return {"type": "null"}
|
|
51
|
+
|
|
52
|
+
# Handle Union types
|
|
53
|
+
if origin is Union or (hasattr(types, 'UnionType') and origin is types.UnionType):
|
|
54
|
+
args = get_args(typ)
|
|
55
|
+
# Check if it's Optional (Union with None)
|
|
56
|
+
if type(None) in args:
|
|
57
|
+
non_none_args = [arg for arg in args if arg is not type(None)]
|
|
58
|
+
if len(non_none_args) == 1:
|
|
59
|
+
# This is Optional[X]
|
|
60
|
+
schema = self._process_type(non_none_args[0])
|
|
61
|
+
return {"anyOf": [schema, {"type": "null"}]}
|
|
62
|
+
# Regular union
|
|
63
|
+
return {"anyOf": [self._process_type(arg) for arg in args]}
|
|
64
|
+
|
|
65
|
+
# Handle List/Sequence types
|
|
66
|
+
if origin in (list, List) or (hasattr(collections.abc, 'Sequence') and origin is collections.abc.Sequence):
|
|
67
|
+
args = get_args(typ)
|
|
68
|
+
item_type = args[0] if args else Any
|
|
69
|
+
return {
|
|
70
|
+
"type": "array",
|
|
71
|
+
"items": self._process_type(item_type)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Handle Dict types
|
|
75
|
+
if origin in (dict, Dict):
|
|
76
|
+
args = get_args(typ)
|
|
77
|
+
if len(args) >= 2:
|
|
78
|
+
return {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"additionalProperties": self._process_type(args[1])
|
|
81
|
+
}
|
|
82
|
+
return {"type": "object"}
|
|
83
|
+
|
|
84
|
+
# Handle Pydantic models
|
|
85
|
+
if isinstance(typ, type) and issubclass(typ, BaseModel):
|
|
86
|
+
type_name = self._get_type_name(typ)
|
|
87
|
+
|
|
88
|
+
# Add to type_defs if not already processed
|
|
89
|
+
if typ not in self.processed_types:
|
|
90
|
+
self.processed_types.add(typ)
|
|
91
|
+
schema = typ.model_json_schema()
|
|
92
|
+
# Remove $defs from individual schemas to avoid nesting
|
|
93
|
+
if '$defs' in schema:
|
|
94
|
+
nested_defs = schema.pop('$defs')
|
|
95
|
+
self.type_defs.update(nested_defs)
|
|
96
|
+
self.type_defs[type_name] = schema
|
|
97
|
+
|
|
98
|
+
return {"$ref": f"#/$defs/{type_name}"}
|
|
99
|
+
|
|
100
|
+
# Fallback for unknown types
|
|
101
|
+
return {"type": "object", "description": f"Unknown type: {typ}"}
|
|
102
|
+
|
|
103
|
+
def _extract_method_info(self, method_desc) -> dict:
|
|
104
|
+
"""Extract parameter and return type information from a method."""
|
|
105
|
+
method_info = {
|
|
106
|
+
"description": method_desc.method.__doc__ or "",
|
|
107
|
+
"parameters": {
|
|
108
|
+
"type": "object",
|
|
109
|
+
"properties": {},
|
|
110
|
+
"required": []
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# Process parameters
|
|
115
|
+
for param in method_desc.params:
|
|
116
|
+
if param.name == 'self':
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
param_schema = self._process_type(param.type)
|
|
120
|
+
method_info["parameters"]["properties"][param.name] = param_schema
|
|
121
|
+
|
|
122
|
+
# Check if parameter is required (not Optional)
|
|
123
|
+
origin = get_origin(param.type)
|
|
124
|
+
args = get_args(param.type)
|
|
125
|
+
is_optional = origin is Union and type(None) in args
|
|
126
|
+
|
|
127
|
+
# Check if parameter has a default value
|
|
128
|
+
has_default = hasattr(param, 'default') and param.default is not None
|
|
129
|
+
|
|
130
|
+
if not is_optional and not has_default:
|
|
131
|
+
method_info["parameters"]["required"].append(param.name)
|
|
132
|
+
|
|
133
|
+
# If no required parameters, remove the empty list
|
|
134
|
+
if not method_info["parameters"]["required"]:
|
|
135
|
+
del method_info["parameters"]["required"]
|
|
136
|
+
|
|
137
|
+
# Process return type
|
|
138
|
+
if method_desc.return_type and method_desc.return_type is not type(None):
|
|
139
|
+
method_info["returns"] = self._process_type(method_desc.return_type)
|
|
140
|
+
else:
|
|
141
|
+
method_info["returns"] = {"type": "null"}
|
|
142
|
+
|
|
143
|
+
return method_info
|
|
144
|
+
|
|
145
|
+
def generate(self) -> dict:
|
|
146
|
+
"""
|
|
147
|
+
Generate a JSON Schema-based service descriptor.
|
|
148
|
+
|
|
149
|
+
Returns a dictionary with:
|
|
150
|
+
- services: Dictionary of service definitions
|
|
151
|
+
- $defs: Reusable type definitions
|
|
152
|
+
"""
|
|
153
|
+
schema = {
|
|
154
|
+
"schemaVersion": "1.0.0",
|
|
155
|
+
"services": {},
|
|
156
|
+
"$defs": {}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
for service_name, service in self.service_manager.descriptors_by_name.items():
|
|
160
|
+
if service.is_component():
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
descriptor = TypeDescriptor.for_type(service.type)
|
|
164
|
+
|
|
165
|
+
service_schema = {
|
|
166
|
+
"name": service_name,
|
|
167
|
+
"type": self._get_type_name(service.type),
|
|
168
|
+
"description": service.type.__doc__ or "",
|
|
169
|
+
"methods": {}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# Process all methods
|
|
173
|
+
for method_desc in descriptor.get_methods():
|
|
174
|
+
method_name = method_desc.method.__name__
|
|
175
|
+
|
|
176
|
+
# Skip private methods
|
|
177
|
+
if method_name.startswith('_'):
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
service_schema["methods"][method_name] = self._extract_method_info(method_desc)
|
|
181
|
+
|
|
182
|
+
schema["services"][service_name] = service_schema
|
|
183
|
+
|
|
184
|
+
# Add all collected type definitions
|
|
185
|
+
schema["$defs"] = self.type_defs
|
|
186
|
+
|
|
187
|
+
return schema
|
|
188
|
+
|
|
189
|
+
def to_json(self, indent: int = 2) -> str:
|
|
190
|
+
"""Generate and serialize the schema to JSON."""
|
|
191
|
+
return json.dumps(self.generate(), indent=indent)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# Import required types
|
|
195
|
+
from typing import Union, List, Dict
|
|
196
|
+
import types
|
|
197
|
+
import collections.abc
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from typing import TypeVar
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from fastapi.openapi.models import OpenAPI, Info, PathItem, Operation, Response, Parameter, RequestBody, \
|
|
5
|
+
MediaType
|
|
6
|
+
|
|
7
|
+
from aspyx.reflection import TypeDescriptor
|
|
8
|
+
from aspyx_service import rest
|
|
9
|
+
from aspyx_service.restchannel import RestChannel
|
|
10
|
+
|
|
11
|
+
T = TypeVar("T")
|
|
12
|
+
|
|
13
|
+
class OpenAPIGenerator:
|
|
14
|
+
PRIMITIVES = {
|
|
15
|
+
str: {"type": "string"},
|
|
16
|
+
int: {"type": "integer", "format": "int32"},
|
|
17
|
+
float: {"type": "number", "format": "float"},
|
|
18
|
+
bool: {"type": "boolean"},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def __init__(self, service_manager):
|
|
22
|
+
self.service_manager = service_manager
|
|
23
|
+
self.rest_channel: RestChannel = service_manager.environment.get(RestChannel)
|
|
24
|
+
self.schemas: dict[type, dict] = {}
|
|
25
|
+
|
|
26
|
+
def _get_schema_for_type(self, typ: type) -> dict:
|
|
27
|
+
if typ in self.schemas:
|
|
28
|
+
return self.schemas[typ]
|
|
29
|
+
|
|
30
|
+
if typ in self.PRIMITIVES:
|
|
31
|
+
schema = self.PRIMITIVES[typ]
|
|
32
|
+
elif isinstance(typ, type) and issubclass(typ, BaseModel):
|
|
33
|
+
schema = typ.model_json_schema()
|
|
34
|
+
else:
|
|
35
|
+
schema = {"type": "object"}
|
|
36
|
+
|
|
37
|
+
self.schemas[typ] = schema
|
|
38
|
+
return schema
|
|
39
|
+
|
|
40
|
+
def generate(self) -> OpenAPI:
|
|
41
|
+
from fastapi.openapi.models import Components
|
|
42
|
+
|
|
43
|
+
openapi = OpenAPI(
|
|
44
|
+
openapi="3.1.0",
|
|
45
|
+
info=Info(title="My API", version="1.0.0"),
|
|
46
|
+
paths={},
|
|
47
|
+
components=Components(schemas={}),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
for service_name, service in self.service_manager.descriptors_by_name.items():
|
|
51
|
+
if service.is_component():
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
descriptor = TypeDescriptor.for_type(service.type)
|
|
55
|
+
|
|
56
|
+
if not descriptor.has_decorator(rest):
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
print(service.type)
|
|
60
|
+
|
|
61
|
+
for method_desc in descriptor.get_methods():
|
|
62
|
+
call = self.rest_channel.get_call(service.type, method_desc.method)
|
|
63
|
+
|
|
64
|
+
#if call.type != "get":
|
|
65
|
+
# continue
|
|
66
|
+
|
|
67
|
+
path_item: PathItem = openapi.paths.get(call.url_template, PathItem())
|
|
68
|
+
operation = Operation(
|
|
69
|
+
responses={"200": Response(description="Success")},
|
|
70
|
+
parameters=[],
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Add path parameters
|
|
74
|
+
for name in call.path_param_names:
|
|
75
|
+
# get type hint
|
|
76
|
+
hint = next((p.type for p in method_desc.params if p.name == name), str)
|
|
77
|
+
operation.parameters.append(
|
|
78
|
+
Parameter(
|
|
79
|
+
name=name,
|
|
80
|
+
**{"in": "path"}, # Use dict unpacking to avoid keyword conflict
|
|
81
|
+
required=True,
|
|
82
|
+
schema=self._get_schema_for_type(hint),
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Add query parameters
|
|
87
|
+
for name in call.query_param_names:
|
|
88
|
+
hint = next((p.type for p in method_desc.params if p.name == name), str)
|
|
89
|
+
operation.parameters.append(
|
|
90
|
+
Parameter(
|
|
91
|
+
name=name,
|
|
92
|
+
**{"in": "query"}, # Use dict unpacking to avoid keyword conflict
|
|
93
|
+
required=True,
|
|
94
|
+
schema=self._get_schema_for_type(hint),
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Add request body
|
|
99
|
+
if call.body_param_name:
|
|
100
|
+
hint = next((p.type for p in method_desc.params if p.name == call.body_param_name), dict)
|
|
101
|
+
operation.request_body = RequestBody(
|
|
102
|
+
required=True,
|
|
103
|
+
content={"application/json": MediaType(schema=self._get_schema_for_type(hint))}
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# attach operation to HTTP method
|
|
107
|
+
setattr(path_item, call.type.lower(), operation)
|
|
108
|
+
openapi.paths[call.url_template] = path_item
|
|
109
|
+
|
|
110
|
+
# attach all cached schemas
|
|
111
|
+
openapi.components.schemas = {k.__name__: v for k, v in self.schemas.items()}
|
|
112
|
+
|
|
113
|
+
return openapi
|
|
114
|
+
|
|
115
|
+
def to_json(self, indent: int = 2) -> str:
|
|
116
|
+
return self.generate().model_dump_json(
|
|
117
|
+
indent=indent,
|
|
118
|
+
exclude_none=True,
|
|
119
|
+
by_alias=True
|
|
120
|
+
)
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""
|
|
2
|
+
health checks
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import logging
|
|
8
|
+
import time
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any, Callable, Type, Optional
|
|
11
|
+
|
|
12
|
+
from aspyx.di import injectable, Environment, inject_environment, on_init
|
|
13
|
+
from aspyx.reflection import Decorators, TypeDescriptor
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def health_checks():
|
|
17
|
+
"""
|
|
18
|
+
Instances of classes that are annotated with @health_checks contain healt mehtods.
|
|
19
|
+
"""
|
|
20
|
+
def decorator(cls):
|
|
21
|
+
Decorators.add(cls, health_checks)
|
|
22
|
+
|
|
23
|
+
#if not Providers.is_registered(cls):
|
|
24
|
+
# Providers.register(ClassInstanceProvider(cls, True, "singleton"))
|
|
25
|
+
|
|
26
|
+
HealthCheckManager.types.append(cls)
|
|
27
|
+
|
|
28
|
+
return cls
|
|
29
|
+
|
|
30
|
+
return decorator
|
|
31
|
+
|
|
32
|
+
def health_check(name="", cache = 0, fail_if_slower_than = 0):
|
|
33
|
+
"""
|
|
34
|
+
Methods annotated with `@health_check` specify health checks that will be executed.
|
|
35
|
+
"""
|
|
36
|
+
def decorator(func):
|
|
37
|
+
Decorators.add(func, health_check, name, cache, fail_if_slower_than)
|
|
38
|
+
return func
|
|
39
|
+
|
|
40
|
+
return decorator
|
|
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
|
+
"""
|
|
50
|
+
OK = 1
|
|
51
|
+
WARNING = 2
|
|
52
|
+
ERROR = 3
|
|
53
|
+
|
|
54
|
+
def __str__(self):
|
|
55
|
+
return self.name
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@injectable()
|
|
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
|
+
"""
|
|
63
|
+
logger = logging.getLogger("aspyx.service.health")
|
|
64
|
+
|
|
65
|
+
# local classes
|
|
66
|
+
|
|
67
|
+
class Check:
|
|
68
|
+
def __init__(self, name: str, cache: int, fail_if_slower_than: int, instance: Any, callable: Callable):
|
|
69
|
+
self.name = name
|
|
70
|
+
self.cache = cache
|
|
71
|
+
self.callable = callable
|
|
72
|
+
self.instance = instance
|
|
73
|
+
self.fail_if_slower_than = fail_if_slower_than
|
|
74
|
+
self.last_check = 0
|
|
75
|
+
|
|
76
|
+
self.last_value : Optional[HealthCheckManager.Result] = None
|
|
77
|
+
|
|
78
|
+
async def run(self, result: HealthCheckManager.Result):
|
|
79
|
+
now = time.time()
|
|
80
|
+
|
|
81
|
+
if self.cache > 0 and self.last_check is not None and now - self.last_check < self.cache:
|
|
82
|
+
result.copy_from(self.last_value)
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
self.last_check = now
|
|
86
|
+
self.last_value = result
|
|
87
|
+
|
|
88
|
+
if asyncio.iscoroutinefunction(self.callable):
|
|
89
|
+
await self.callable(self.instance, result)
|
|
90
|
+
else:
|
|
91
|
+
await asyncio.to_thread(self.callable, self.instance, result)
|
|
92
|
+
|
|
93
|
+
spent = time.time() - now
|
|
94
|
+
|
|
95
|
+
if result.status == HealthStatus.OK and 0 < self.fail_if_slower_than < spent:
|
|
96
|
+
result.status = HealthStatus.ERROR
|
|
97
|
+
result.details = f"spent {spent:.3f}s"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class Result:
|
|
101
|
+
def __init__(self, name: str):
|
|
102
|
+
self.status = HealthStatus.OK
|
|
103
|
+
self.name = name
|
|
104
|
+
self.details = ""
|
|
105
|
+
|
|
106
|
+
def copy_from(self, value: HealthCheckManager.Result):
|
|
107
|
+
self.status = value.status
|
|
108
|
+
self.details = value.details
|
|
109
|
+
|
|
110
|
+
def set_status(self, status: HealthStatus, details =""):
|
|
111
|
+
self.status = status
|
|
112
|
+
self.details = details
|
|
113
|
+
|
|
114
|
+
def to_dict(self):
|
|
115
|
+
result = {
|
|
116
|
+
"name": self.name,
|
|
117
|
+
"status": str(self.status),
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if self.details:
|
|
121
|
+
result["details"] = self.details
|
|
122
|
+
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
class Health:
|
|
126
|
+
def __init__(self, status: HealthStatus = HealthStatus.OK):
|
|
127
|
+
self.status = status
|
|
128
|
+
self.results : list[HealthCheckManager.Result] = []
|
|
129
|
+
|
|
130
|
+
def to_dict(self):
|
|
131
|
+
return {
|
|
132
|
+
"status": str(self.status),
|
|
133
|
+
"checks": [result.to_dict() for result in self.results]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# class data
|
|
137
|
+
|
|
138
|
+
types : list[Type] = []
|
|
139
|
+
|
|
140
|
+
# constructor
|
|
141
|
+
|
|
142
|
+
def __init__(self):
|
|
143
|
+
self.environment : Optional[Environment] = None
|
|
144
|
+
self.checks: list[HealthCheckManager.Check] = []
|
|
145
|
+
|
|
146
|
+
# check
|
|
147
|
+
|
|
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
|
+
"""
|
|
154
|
+
self.logger.info("Checking health...")
|
|
155
|
+
|
|
156
|
+
health = HealthCheckManager.Health()
|
|
157
|
+
|
|
158
|
+
tasks = []
|
|
159
|
+
for check in self.checks:
|
|
160
|
+
result = HealthCheckManager.Result(check.name)
|
|
161
|
+
health.results.append(result)
|
|
162
|
+
tasks.append(check.run(result))
|
|
163
|
+
|
|
164
|
+
await asyncio.gather(*tasks)
|
|
165
|
+
|
|
166
|
+
for result in health.results:
|
|
167
|
+
if result.status.value > health.status.value:
|
|
168
|
+
health.status = result.status
|
|
169
|
+
|
|
170
|
+
return health
|
|
171
|
+
|
|
172
|
+
# public
|
|
173
|
+
|
|
174
|
+
@inject_environment()
|
|
175
|
+
def set_environment(self, environment: Environment):
|
|
176
|
+
self.environment = environment
|
|
177
|
+
|
|
178
|
+
@on_init()
|
|
179
|
+
def setup(self):
|
|
180
|
+
for type in self.types:
|
|
181
|
+
descriptor = TypeDescriptor(type).for_type(type)
|
|
182
|
+
instance = self.environment.get(type)
|
|
183
|
+
|
|
184
|
+
for method in descriptor.get_methods():
|
|
185
|
+
if method.has_decorator(health_check):
|
|
186
|
+
decorator = method.get_decorator(health_check)
|
|
187
|
+
|
|
188
|
+
name = decorator.args[0]
|
|
189
|
+
cache = decorator.args[1]
|
|
190
|
+
fail_if_slower_than = decorator.args[2]
|
|
191
|
+
if len(name) == 0:
|
|
192
|
+
name = method.get_name()
|
|
193
|
+
|
|
194
|
+
self.checks.append(HealthCheckManager.Check(name, cache, fail_if_slower_than, instance, method.method))
|