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,241 @@
|
|
|
1
|
+
"""
|
|
2
|
+
registries for components in aspyx service
|
|
3
|
+
"""
|
|
4
|
+
import re
|
|
5
|
+
import threading
|
|
6
|
+
from abc import abstractmethod
|
|
7
|
+
import time
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
import consul
|
|
11
|
+
|
|
12
|
+
from aspyx.di.configuration import inject_value
|
|
13
|
+
from aspyx.util import StringBuilder
|
|
14
|
+
from aspyx.di import on_init
|
|
15
|
+
from .healthcheck import HealthCheckManager, HealthStatus
|
|
16
|
+
|
|
17
|
+
from .server import Server
|
|
18
|
+
from .service import ComponentRegistry, Channel, ChannelInstances, ServiceManager, ComponentDescriptor, Component, ChannelAddress
|
|
19
|
+
|
|
20
|
+
class ConsulComponentRegistry(ComponentRegistry):
|
|
21
|
+
"""
|
|
22
|
+
A specialized registry using consul.
|
|
23
|
+
A polling mechanism is used to identify changes in the component health.
|
|
24
|
+
"""
|
|
25
|
+
# constructor
|
|
26
|
+
|
|
27
|
+
def __init__(self, port: int, consul: consul.Consul):
|
|
28
|
+
self.port = port
|
|
29
|
+
self.ip = Server.get_local_ip()
|
|
30
|
+
self.running = False
|
|
31
|
+
self.consul = consul
|
|
32
|
+
self.watchdog = None
|
|
33
|
+
self.interval = 5
|
|
34
|
+
self.last_index = {}
|
|
35
|
+
self.component_addresses : dict[str, dict[str, ChannelInstances]] = {} # comp -> channel -> address
|
|
36
|
+
self.watch_channels : list[Channel] = []
|
|
37
|
+
self.watchdog_interval = 5
|
|
38
|
+
self.healthcheck_interval = "10s"
|
|
39
|
+
self.healthcheck_timeout= "5s"
|
|
40
|
+
self.healthcheck_deregister = "5m"
|
|
41
|
+
|
|
42
|
+
# injections
|
|
43
|
+
|
|
44
|
+
@inject_value("consul.watchdog.interval", default=5)
|
|
45
|
+
def set_watchdog_interval(self, interval):
|
|
46
|
+
self.watchdog_interval = interval
|
|
47
|
+
|
|
48
|
+
@inject_value("consul.healthcheck.interval", default="10s")
|
|
49
|
+
def set_healthcheck_interval(self, interval):
|
|
50
|
+
self.healthcheck_interval = interval
|
|
51
|
+
|
|
52
|
+
@inject_value("consul.healthcheck.timeout", default="3s")
|
|
53
|
+
def set_healthcheck_timeout(self, interval):
|
|
54
|
+
self.healthcheck_timeout = interval
|
|
55
|
+
|
|
56
|
+
@inject_value("consul.healthcheck.deregister", default="5m")
|
|
57
|
+
def set_healthcheck_deregister(self, interval):
|
|
58
|
+
self.healthcheck_deregister = interval
|
|
59
|
+
|
|
60
|
+
# lifecycle hooks
|
|
61
|
+
|
|
62
|
+
@on_init()
|
|
63
|
+
def setup(self):
|
|
64
|
+
# create consul client
|
|
65
|
+
|
|
66
|
+
self.running = True
|
|
67
|
+
|
|
68
|
+
# start thread
|
|
69
|
+
|
|
70
|
+
self.watchdog = threading.Thread(target=self.watch_consul, daemon=True)
|
|
71
|
+
self.watchdog.start()
|
|
72
|
+
|
|
73
|
+
def inform_channels(self, old_address: ChannelInstances, new_address: Optional[ChannelInstances]):
|
|
74
|
+
for channel in self.watch_channels:
|
|
75
|
+
if channel.address is old_address:
|
|
76
|
+
channel.set_address(new_address)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def watch_consul(self):
|
|
80
|
+
while self.running:
|
|
81
|
+
# check services
|
|
82
|
+
|
|
83
|
+
for component, old_addresses in self.component_addresses.items():
|
|
84
|
+
old_addresses = dict(old_addresses) # we will modify it...
|
|
85
|
+
new_addresses = self.fetch_addresses(component, wait="1s")
|
|
86
|
+
|
|
87
|
+
# compare
|
|
88
|
+
|
|
89
|
+
changed = False
|
|
90
|
+
for channel_name, address in new_addresses.items():
|
|
91
|
+
service_address = old_addresses.get(channel_name, None)
|
|
92
|
+
if service_address is None:
|
|
93
|
+
ServiceManager.logger.info("new %s address for %s", channel_name, component)
|
|
94
|
+
changed = True
|
|
95
|
+
else:
|
|
96
|
+
if service_address != address:
|
|
97
|
+
changed = True
|
|
98
|
+
|
|
99
|
+
ServiceManager.logger.info("%s address for %s changed", channel_name, component)
|
|
100
|
+
|
|
101
|
+
# inform channels
|
|
102
|
+
|
|
103
|
+
self.inform_channels(service_address, address)
|
|
104
|
+
|
|
105
|
+
# delete
|
|
106
|
+
|
|
107
|
+
del old_addresses[channel_name]
|
|
108
|
+
|
|
109
|
+
# watchout for deleted addresses
|
|
110
|
+
|
|
111
|
+
for channel_name, address in old_addresses.items():
|
|
112
|
+
ServiceManager.logger.info("deleted %s address for %s", channel_name, component)
|
|
113
|
+
|
|
114
|
+
changed = True
|
|
115
|
+
|
|
116
|
+
# inform channel
|
|
117
|
+
|
|
118
|
+
self.inform_channels(address, None)
|
|
119
|
+
|
|
120
|
+
# replace ( does that work while iterating )
|
|
121
|
+
|
|
122
|
+
if changed:
|
|
123
|
+
self.component_addresses[component] = new_addresses
|
|
124
|
+
|
|
125
|
+
# time to sleep
|
|
126
|
+
|
|
127
|
+
time.sleep(self.watchdog_interval)
|
|
128
|
+
|
|
129
|
+
@abstractmethod
|
|
130
|
+
def watch(self, channel: Channel) -> None:
|
|
131
|
+
self.watch_channels.append(channel)
|
|
132
|
+
|
|
133
|
+
# public
|
|
134
|
+
|
|
135
|
+
def register_service(self, name, service_id, health: str, tags=None, meta=None) -> None:
|
|
136
|
+
self.consul.agent.service.register(
|
|
137
|
+
name=name,
|
|
138
|
+
service_id=service_id,
|
|
139
|
+
address=self.ip,
|
|
140
|
+
port=self.port,
|
|
141
|
+
tags=tags or [],
|
|
142
|
+
meta=meta or {},
|
|
143
|
+
check=consul.Check().http(
|
|
144
|
+
url=f"http://{self.ip}:{self.port}{health}",
|
|
145
|
+
interval=self.healthcheck_interval,
|
|
146
|
+
timeout=self.healthcheck_timeout,
|
|
147
|
+
deregister=self.healthcheck_deregister)
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def deregister(self, descriptor: ComponentDescriptor[Component]) -> None:
|
|
151
|
+
name = descriptor.name
|
|
152
|
+
|
|
153
|
+
service_id = f"{self.ip}:{self.port}:{name}"
|
|
154
|
+
|
|
155
|
+
self.consul.agent.service.deregister(service_id)
|
|
156
|
+
|
|
157
|
+
def stop(self):
|
|
158
|
+
self.running = False
|
|
159
|
+
if self.watchdog is not None:
|
|
160
|
+
self.watchdog.join()
|
|
161
|
+
self.watchdog = None
|
|
162
|
+
|
|
163
|
+
# private
|
|
164
|
+
|
|
165
|
+
def fetch_addresses(self, component: str, wait=None) -> dict[str, ChannelInstances]:
|
|
166
|
+
addresses : dict[str, ChannelInstances] = {} # channel name -> ServiceAddress
|
|
167
|
+
|
|
168
|
+
index, nodes = self.consul.health.service(component, passing=True, index=self.last_index.get(component, None), wait=wait)
|
|
169
|
+
self.last_index[component] = index
|
|
170
|
+
|
|
171
|
+
for node in nodes:
|
|
172
|
+
service = node["Service"]
|
|
173
|
+
|
|
174
|
+
meta = service.get('Meta')
|
|
175
|
+
|
|
176
|
+
channels = meta.get("channels").split(",")
|
|
177
|
+
|
|
178
|
+
for channel in channels:
|
|
179
|
+
match = re.search(r"([\w-]+)\((.*)\)", channel)
|
|
180
|
+
|
|
181
|
+
channel_name = match.group(1)
|
|
182
|
+
url = match.group(2)
|
|
183
|
+
|
|
184
|
+
address = addresses.get(channel, None)
|
|
185
|
+
if address is None:
|
|
186
|
+
address = ChannelInstances(component=component, channel=channel_name)
|
|
187
|
+
addresses[channel] = address
|
|
188
|
+
|
|
189
|
+
address.urls.append(url)
|
|
190
|
+
|
|
191
|
+
# done
|
|
192
|
+
|
|
193
|
+
return addresses
|
|
194
|
+
|
|
195
|
+
# implement ComponentRegistry
|
|
196
|
+
|
|
197
|
+
def register(self, descriptor: ComponentDescriptor[Component], addresses: list[ChannelAddress]):
|
|
198
|
+
name = descriptor.name
|
|
199
|
+
|
|
200
|
+
id = f"{self.ip}:{self.port}:{name}"
|
|
201
|
+
|
|
202
|
+
builder = StringBuilder()
|
|
203
|
+
first = True
|
|
204
|
+
for address in addresses:
|
|
205
|
+
if not first:
|
|
206
|
+
builder.append(",")
|
|
207
|
+
|
|
208
|
+
builder.append(address.channel).append("(").append(address.uri).append(")")
|
|
209
|
+
|
|
210
|
+
first = False
|
|
211
|
+
|
|
212
|
+
addresses = str(builder)
|
|
213
|
+
|
|
214
|
+
self.register_service(name, id, descriptor.health, tags =["component"], meta={"channels": addresses})
|
|
215
|
+
|
|
216
|
+
def get_addresses(self, descriptor: ComponentDescriptor) -> list[ChannelInstances]:
|
|
217
|
+
component_addresses = self.component_addresses.get(descriptor.name, None)
|
|
218
|
+
if component_addresses is None:
|
|
219
|
+
component_addresses = self.fetch_addresses(descriptor.name)
|
|
220
|
+
|
|
221
|
+
# only cache if non-empty
|
|
222
|
+
|
|
223
|
+
if component_addresses:
|
|
224
|
+
self.component_addresses[descriptor.name] = component_addresses
|
|
225
|
+
|
|
226
|
+
return list(component_addresses.values())
|
|
227
|
+
|
|
228
|
+
# 200–299 passing Service is healthy (OK, Created, No Content…)
|
|
229
|
+
# 429 warning Rate limited or degraded
|
|
230
|
+
# 300–399 warning Redirects interpreted as potential issues
|
|
231
|
+
# 400–499 critical Client errors (Bad Request, Unauthorized…)
|
|
232
|
+
# 500–599 critical Server errors (Internal Error, Timeout…)
|
|
233
|
+
# Other / No response critical Timeout, connection refused, etc.
|
|
234
|
+
|
|
235
|
+
def map_health(self, health: HealthCheckManager.Health) -> int:
|
|
236
|
+
if health.status is HealthStatus.OK:
|
|
237
|
+
return 200
|
|
238
|
+
elif health.status is HealthStatus.WARNING:
|
|
239
|
+
return 429
|
|
240
|
+
else:
|
|
241
|
+
return 500
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""
|
|
2
|
+
rest channel implementation
|
|
3
|
+
"""
|
|
4
|
+
import inspect
|
|
5
|
+
import re
|
|
6
|
+
from dataclasses import is_dataclass
|
|
7
|
+
|
|
8
|
+
from typing import get_type_hints, TypeVar, Annotated, Callable, get_origin, get_args, Type
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
from aspyx.reflection import DynamicProxy, Decorators
|
|
12
|
+
from aspyx.util import get_serializer
|
|
13
|
+
|
|
14
|
+
from .channels import HTTPXChannel
|
|
15
|
+
from .service import channel, ServiceCommunicationException
|
|
16
|
+
|
|
17
|
+
T = TypeVar("T")
|
|
18
|
+
|
|
19
|
+
class BodyMarker:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
Body = lambda t: Annotated[t, BodyMarker]
|
|
23
|
+
|
|
24
|
+
class QueryParamMarker:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
QueryParam = lambda t: Annotated[t, QueryParamMarker]
|
|
28
|
+
|
|
29
|
+
# decorators
|
|
30
|
+
|
|
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
|
+
"""
|
|
38
|
+
def decorator(cls):
|
|
39
|
+
Decorators.add(cls, rest, url)
|
|
40
|
+
|
|
41
|
+
return cls
|
|
42
|
+
return decorator
|
|
43
|
+
|
|
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
|
+
"""
|
|
51
|
+
def decorator(cls):
|
|
52
|
+
Decorators.add(cls, get, url)
|
|
53
|
+
|
|
54
|
+
return cls
|
|
55
|
+
return decorator
|
|
56
|
+
|
|
57
|
+
|
|
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
|
+
"""
|
|
66
|
+
def decorator(cls):
|
|
67
|
+
Decorators.add(cls, post, url)
|
|
68
|
+
|
|
69
|
+
return cls
|
|
70
|
+
|
|
71
|
+
return decorator
|
|
72
|
+
|
|
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
|
+
"""
|
|
80
|
+
def decorator(cls):
|
|
81
|
+
Decorators.add(cls, put, url)
|
|
82
|
+
|
|
83
|
+
return cls
|
|
84
|
+
|
|
85
|
+
return decorator
|
|
86
|
+
|
|
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
|
+
"""
|
|
94
|
+
def decorator(cls):
|
|
95
|
+
Decorators.add(cls, delete, url)
|
|
96
|
+
|
|
97
|
+
return cls
|
|
98
|
+
|
|
99
|
+
return decorator
|
|
100
|
+
|
|
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
|
+
"""
|
|
108
|
+
def decorator(cls):
|
|
109
|
+
Decorators.add(cls, patch, url)
|
|
110
|
+
|
|
111
|
+
return cls
|
|
112
|
+
|
|
113
|
+
return decorator
|
|
114
|
+
|
|
115
|
+
@channel("rest")
|
|
116
|
+
class RestChannel(HTTPXChannel):
|
|
117
|
+
"""
|
|
118
|
+
A rest channel executes http requests as specified by the corresponding decorators and annotations,
|
|
119
|
+
"""
|
|
120
|
+
__slots__ = [
|
|
121
|
+
"signature",
|
|
122
|
+
"url_template",
|
|
123
|
+
"type",
|
|
124
|
+
"calls",
|
|
125
|
+
"return_type",
|
|
126
|
+
"path_param_names",
|
|
127
|
+
"query_param_names",
|
|
128
|
+
"body_param_name"
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
# local class
|
|
132
|
+
|
|
133
|
+
class Call:
|
|
134
|
+
# slots
|
|
135
|
+
|
|
136
|
+
__slots__ = [
|
|
137
|
+
"type",
|
|
138
|
+
"url_template",
|
|
139
|
+
"path_param_names",
|
|
140
|
+
"body_param_name",
|
|
141
|
+
"query_param_names",
|
|
142
|
+
"return_type",
|
|
143
|
+
"signature",
|
|
144
|
+
"body_serializer"
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
# constructor
|
|
148
|
+
|
|
149
|
+
def __init__(self, type: Type, method : Callable):
|
|
150
|
+
self.signature = inspect.signature(method)
|
|
151
|
+
|
|
152
|
+
type_hints = get_type_hints(method)
|
|
153
|
+
|
|
154
|
+
param_names = list(self.signature.parameters.keys())
|
|
155
|
+
param_names.remove("self")
|
|
156
|
+
|
|
157
|
+
prefix = ""
|
|
158
|
+
if Decorators.has_decorator(type, rest):
|
|
159
|
+
prefix = Decorators.get_decorator(type, rest).args[0]
|
|
160
|
+
|
|
161
|
+
# find decorator
|
|
162
|
+
|
|
163
|
+
self.type = "get"
|
|
164
|
+
self.url_template = ""
|
|
165
|
+
|
|
166
|
+
decorators = Decorators.get_all(method)
|
|
167
|
+
|
|
168
|
+
for decorator in [get, post, put, delete, patch]:
|
|
169
|
+
descriptor = next((descriptor for descriptor in decorators if descriptor.decorator is decorator), None)
|
|
170
|
+
if descriptor is not None:
|
|
171
|
+
self.type = decorator.__name__
|
|
172
|
+
self.url_template = prefix + descriptor.args[0]
|
|
173
|
+
|
|
174
|
+
# parameters
|
|
175
|
+
|
|
176
|
+
self.path_param_names = set(re.findall(r"{(.*?)}", self.url_template))
|
|
177
|
+
|
|
178
|
+
for param_name in self.path_param_names:
|
|
179
|
+
param_names.remove(param_name)
|
|
180
|
+
|
|
181
|
+
hints = get_type_hints(method, include_extras=True)
|
|
182
|
+
|
|
183
|
+
self.body_param_name = None
|
|
184
|
+
self.query_param_names = set()
|
|
185
|
+
|
|
186
|
+
for param_name, hint in hints.items():
|
|
187
|
+
if get_origin(hint) is Annotated:
|
|
188
|
+
metadata = get_args(hint)[1:]
|
|
189
|
+
|
|
190
|
+
if BodyMarker in metadata:
|
|
191
|
+
self.body_param_name = param_name
|
|
192
|
+
self.body_serializer = get_serializer(type_hints[param_name])
|
|
193
|
+
param_names.remove(param_name)
|
|
194
|
+
elif QueryParamMarker in metadata:
|
|
195
|
+
self.query_param_names.add(param_name)
|
|
196
|
+
param_names.remove(param_name)
|
|
197
|
+
|
|
198
|
+
# check if something is missing
|
|
199
|
+
|
|
200
|
+
if param_names:
|
|
201
|
+
# check body params
|
|
202
|
+
if self.type in ("post", "put", "patch"):
|
|
203
|
+
if self.body_param_name is None:
|
|
204
|
+
candidates = [
|
|
205
|
+
(name, hint)
|
|
206
|
+
for name, hint in hints.items()
|
|
207
|
+
if name not in self.path_param_names
|
|
208
|
+
]
|
|
209
|
+
# find first dataclass or pydantic argument
|
|
210
|
+
for name, hint in candidates:
|
|
211
|
+
typ = hint
|
|
212
|
+
if get_origin(typ) is Annotated:
|
|
213
|
+
typ = get_args(typ)[0]
|
|
214
|
+
if (
|
|
215
|
+
(isinstance(typ, type) and issubclass(typ, BaseModel))
|
|
216
|
+
or is_dataclass(typ)
|
|
217
|
+
):
|
|
218
|
+
self.body_param_name = name
|
|
219
|
+
self.body_serializer = get_serializer(type_hints[name])
|
|
220
|
+
param_names.remove(name)
|
|
221
|
+
break
|
|
222
|
+
|
|
223
|
+
# the rest are query params
|
|
224
|
+
|
|
225
|
+
for param in param_names:
|
|
226
|
+
self.query_param_names.add(param)
|
|
227
|
+
|
|
228
|
+
# return type
|
|
229
|
+
|
|
230
|
+
self.return_type = type_hints['return']
|
|
231
|
+
|
|
232
|
+
# constructor
|
|
233
|
+
|
|
234
|
+
def __init__(self):
|
|
235
|
+
super().__init__()
|
|
236
|
+
|
|
237
|
+
self.calls : dict[Callable, RestChannel.Call] = {}
|
|
238
|
+
|
|
239
|
+
# internal
|
|
240
|
+
|
|
241
|
+
def get_call(self, type: Type ,method: Callable):
|
|
242
|
+
call = self.calls.get(method, None)
|
|
243
|
+
if call is None:
|
|
244
|
+
call = RestChannel.Call(type, method)
|
|
245
|
+
self.calls[method] = call
|
|
246
|
+
|
|
247
|
+
return call
|
|
248
|
+
|
|
249
|
+
# override
|
|
250
|
+
|
|
251
|
+
async def invoke_async(self, invocation: 'DynamicProxy.Invocation'):
|
|
252
|
+
call = self.get_call(invocation.type, invocation.method)
|
|
253
|
+
|
|
254
|
+
bound = call.signature.bind(self, *invocation.args, **invocation.kwargs)
|
|
255
|
+
bound.apply_defaults()
|
|
256
|
+
arguments = bound.arguments
|
|
257
|
+
|
|
258
|
+
# url
|
|
259
|
+
|
|
260
|
+
url = call.url_template.format(**arguments)
|
|
261
|
+
query_params = {k: arguments[k] for k in call.query_param_names if k in arguments}
|
|
262
|
+
body = {}
|
|
263
|
+
if call.body_param_name is not None:
|
|
264
|
+
body = call.body_serializer(arguments.get(call.body_param_name))#self.to_dict(arguments.get(call.body_param_name))
|
|
265
|
+
|
|
266
|
+
# call
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
result = None
|
|
270
|
+
if call.type in ["get", "put", "delete"]:
|
|
271
|
+
result = await self.request_async(call.type, self.get_url() + url, params=query_params, timeout=self.timeout)
|
|
272
|
+
|
|
273
|
+
elif call.type == "post":
|
|
274
|
+
result = await self.request_async("post", self.get_url() + url, params=query_params, json=body, timeout=self.timeout)
|
|
275
|
+
|
|
276
|
+
return self.get_deserializer(invocation.type, invocation.method)(result.json())
|
|
277
|
+
except ServiceCommunicationException:
|
|
278
|
+
raise
|
|
279
|
+
|
|
280
|
+
except Exception as e:
|
|
281
|
+
raise ServiceCommunicationException(f"communication exception {e}") from e
|
|
282
|
+
|
|
283
|
+
def invoke(self, invocation: DynamicProxy.Invocation):
|
|
284
|
+
call = self.get_call(invocation.type, invocation.method)
|
|
285
|
+
|
|
286
|
+
bound = call.signature.bind(self,*invocation.args, **invocation.kwargs)
|
|
287
|
+
bound.apply_defaults()
|
|
288
|
+
arguments = bound.arguments
|
|
289
|
+
|
|
290
|
+
# url
|
|
291
|
+
|
|
292
|
+
url = call.url_template.format(**arguments)
|
|
293
|
+
query_params = {k: arguments[k] for k in call.query_param_names if k in arguments}
|
|
294
|
+
body = {}
|
|
295
|
+
if call.body_param_name is not None:
|
|
296
|
+
body = call.body_serializer(arguments.get(call.body_param_name))#self.to_dict(arguments.get(call.body_param_name))
|
|
297
|
+
|
|
298
|
+
# call
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
result = None
|
|
302
|
+
if call.type in ["get", "put", "delete"]:
|
|
303
|
+
result = self.request(call.type, self.get_url() + url, params=query_params, timeout=self.timeout)
|
|
304
|
+
|
|
305
|
+
elif call.type == "post":
|
|
306
|
+
result = self.request( "post", self.get_url() + url, params=query_params, json=body, timeout=self.timeout)
|
|
307
|
+
|
|
308
|
+
return self.get_deserializer(invocation.type, invocation.method)(result.json())
|
|
309
|
+
except ServiceCommunicationException:
|
|
310
|
+
raise
|
|
311
|
+
|
|
312
|
+
except Exception as e:
|
|
313
|
+
raise ServiceCommunicationException(f"communication exception {e}") from e
|