aspyx-service 0.10.2__py3-none-any.whl → 0.10.3__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/channels.py +18 -13
- aspyx_service/restchannel.py +10 -11
- aspyx_service/serialization.py +3 -0
- aspyx_service/service.py +8 -7
- {aspyx_service-0.10.2.dist-info → aspyx_service-0.10.3.dist-info}/METADATA +11 -7
- aspyx_service-0.10.3.dist-info/RECORD +12 -0
- aspyx_service-0.10.2.dist-info/RECORD +0 -12
- {aspyx_service-0.10.2.dist-info → aspyx_service-0.10.3.dist-info}/WHEEL +0 -0
- {aspyx_service-0.10.2.dist-info → aspyx_service-0.10.3.dist-info}/licenses/LICENSE +0 -0
aspyx_service/channels.py
CHANGED
|
@@ -13,6 +13,7 @@ from pydantic import BaseModel
|
|
|
13
13
|
|
|
14
14
|
from aspyx.di.configuration import inject_value
|
|
15
15
|
from aspyx.reflection import DynamicProxy, TypeDescriptor
|
|
16
|
+
from aspyx.threading import ThreadLocal
|
|
16
17
|
from .service import ServiceManager, ServiceCommunicationException
|
|
17
18
|
|
|
18
19
|
from .service import ComponentDescriptor, ChannelInstances, ServiceException, channel, Channel, RemoteServiceException
|
|
@@ -28,6 +29,11 @@ class HTTPXChannel(Channel):
|
|
|
28
29
|
"timeout"
|
|
29
30
|
]
|
|
30
31
|
|
|
32
|
+
# class properties
|
|
33
|
+
|
|
34
|
+
client_local = ThreadLocal[Client]()
|
|
35
|
+
async_client_local = ThreadLocal[AsyncClient]()
|
|
36
|
+
|
|
31
37
|
# class methods
|
|
32
38
|
|
|
33
39
|
@classmethod
|
|
@@ -61,8 +67,6 @@ class HTTPXChannel(Channel):
|
|
|
61
67
|
super().__init__()
|
|
62
68
|
|
|
63
69
|
self.timeout = 1000.0
|
|
64
|
-
self.client: Optional[Client] = None
|
|
65
|
-
self.async_client: Optional[AsyncClient] = None
|
|
66
70
|
self.service_names: dict[Type, str] = {}
|
|
67
71
|
self.deserializers: dict[Callable, Callable] = {}
|
|
68
72
|
|
|
@@ -95,24 +99,25 @@ class HTTPXChannel(Channel):
|
|
|
95
99
|
for service in component_descriptor.services:
|
|
96
100
|
self.service_names[service.type] = service.name
|
|
97
101
|
|
|
98
|
-
# make client
|
|
99
|
-
|
|
100
|
-
self.client = self.make_client()
|
|
101
|
-
self.async_client = self.make_async_client()
|
|
102
|
-
|
|
103
102
|
# public
|
|
104
103
|
|
|
105
104
|
def get_client(self) -> Client:
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
client = self.client_local.get()
|
|
106
|
+
|
|
107
|
+
if client is None:
|
|
108
|
+
client = self.make_client()
|
|
109
|
+
self.client_local.set(client)
|
|
108
110
|
|
|
109
|
-
return
|
|
111
|
+
return client
|
|
110
112
|
|
|
111
113
|
def get_async_client(self) -> AsyncClient:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
async_client = self.async_client_local.get()
|
|
115
|
+
|
|
116
|
+
if async_client is None:
|
|
117
|
+
async_client = self.make_async_client()
|
|
118
|
+
self.async_client_local.set(async_client)
|
|
114
119
|
|
|
115
|
-
return
|
|
120
|
+
return async_client
|
|
116
121
|
|
|
117
122
|
def make_client(self) -> Client:
|
|
118
123
|
return Client() # base_url=url
|
aspyx_service/restchannel.py
CHANGED
|
@@ -249,16 +249,15 @@ class RestChannel(HTTPXChannel):
|
|
|
249
249
|
try:
|
|
250
250
|
result = None
|
|
251
251
|
if call.type == "get":
|
|
252
|
-
result = await self.
|
|
252
|
+
result = await self.get_async_client().get(self.get_url() + url, params=query_params, timeout=self.timeout)
|
|
253
253
|
elif call.type == "put":
|
|
254
|
-
result = await self.
|
|
254
|
+
result = await self.get_async_client().put(self.get_url() + url, params=query_params, timeout=self.timeout)
|
|
255
255
|
elif call.type == "delete":
|
|
256
|
-
result = await self.
|
|
256
|
+
result = await self.get_async_client().delete(self.get_url() + url, params=query_params, timeout=self.timeout)
|
|
257
257
|
elif call.type == "post":
|
|
258
|
-
result = await self.
|
|
259
|
-
timeout=self.timeout).json()
|
|
258
|
+
result = await self.get_async_client().post(self.get_url() + url, params=query_params, json=body, timeout=self.timeout)
|
|
260
259
|
|
|
261
|
-
return self.get_deserializer(invocation.type, invocation.method)(result)
|
|
260
|
+
return self.get_deserializer(invocation.type, invocation.method)(result.json())
|
|
262
261
|
except ServiceCommunicationException:
|
|
263
262
|
raise
|
|
264
263
|
|
|
@@ -285,15 +284,15 @@ class RestChannel(HTTPXChannel):
|
|
|
285
284
|
try:
|
|
286
285
|
result = None
|
|
287
286
|
if call.type == "get":
|
|
288
|
-
result = self.get_client().get(self.get_url() + url, params=query_params, timeout=self.timeout)
|
|
287
|
+
result = self.get_client().get(self.get_url() + url, params=query_params, timeout=self.timeout)
|
|
289
288
|
elif call.type == "put":
|
|
290
|
-
result = self.get_client().put(self.get_url() + url, params=query_params, timeout=self.timeout)
|
|
289
|
+
result = self.get_client().put(self.get_url() + url, params=query_params, timeout=self.timeout)
|
|
291
290
|
elif call.type == "delete":
|
|
292
|
-
result = self.get_client().delete(self.get_url() + url, params=query_params, timeout=self.timeout)
|
|
291
|
+
result = self.get_client().delete(self.get_url() + url, params=query_params, timeout=self.timeout)
|
|
293
292
|
elif call.type == "post":
|
|
294
|
-
result = self.get_client().post(self.get_url() + url, params=query_params, json=body, timeout=self.timeout)
|
|
293
|
+
result = self.get_client().post(self.get_url() + url, params=query_params, json=body, timeout=self.timeout)
|
|
295
294
|
|
|
296
|
-
return self.get_deserializer(invocation.type, invocation.method)(result)
|
|
295
|
+
return self.get_deserializer(invocation.type, invocation.method)(result.json())
|
|
297
296
|
except ServiceCommunicationException:
|
|
298
297
|
raise
|
|
299
298
|
|
aspyx_service/serialization.py
CHANGED
|
@@ -42,6 +42,9 @@ class TypeDeserializer:
|
|
|
42
42
|
if is_dataclass(typ):
|
|
43
43
|
field_deserializers = {f.name: TypeDeserializer(f.type) for f in fields(typ)}
|
|
44
44
|
def deser_dataclass(value):
|
|
45
|
+
if is_dataclass(value):
|
|
46
|
+
return value
|
|
47
|
+
|
|
45
48
|
return typ(**{
|
|
46
49
|
k: field_deserializers[k](v) for k, v in value.items()
|
|
47
50
|
})
|
aspyx_service/service.py
CHANGED
|
@@ -769,11 +769,12 @@ class ServiceManager:
|
|
|
769
769
|
|
|
770
770
|
# check proxy
|
|
771
771
|
|
|
772
|
-
|
|
772
|
+
channel_key = TypeAndChannel(type=component_descriptor.type, channel=preferred_channel)
|
|
773
|
+
proxy_key = TypeAndChannel(type=service_type, channel=preferred_channel)
|
|
773
774
|
|
|
774
|
-
proxy = self.proxy_cache.get(
|
|
775
|
+
proxy = self.proxy_cache.get(proxy_key, None)
|
|
775
776
|
if proxy is None:
|
|
776
|
-
channel_instance = self.channel_cache.get(
|
|
777
|
+
channel_instance = self.channel_cache.get(channel_key, None)
|
|
777
778
|
|
|
778
779
|
if channel_instance is None:
|
|
779
780
|
address = self.find_service_address(component_descriptor, preferred_channel)
|
|
@@ -786,9 +787,9 @@ class ServiceManager:
|
|
|
786
787
|
# channel may have changed
|
|
787
788
|
|
|
788
789
|
if address.channel != preferred_channel:
|
|
789
|
-
|
|
790
|
+
channel_key = TypeAndChannel(type=component_descriptor.type, channel=address.channel)
|
|
790
791
|
|
|
791
|
-
channel_instance = self.channel_cache.get(
|
|
792
|
+
channel_instance = self.channel_cache.get(channel_key, None)
|
|
792
793
|
if channel_instance is None:
|
|
793
794
|
# create channel
|
|
794
795
|
|
|
@@ -796,7 +797,7 @@ class ServiceManager:
|
|
|
796
797
|
|
|
797
798
|
# cache
|
|
798
799
|
|
|
799
|
-
self.channel_cache[
|
|
800
|
+
self.channel_cache[channel_key] = channel_instance
|
|
800
801
|
|
|
801
802
|
# and watch for changes in the addresses
|
|
802
803
|
|
|
@@ -805,7 +806,7 @@ class ServiceManager:
|
|
|
805
806
|
# create proxy
|
|
806
807
|
|
|
807
808
|
proxy = DynamicProxy.create(service_type, channel_instance)
|
|
808
|
-
self.proxy_cache[
|
|
809
|
+
self.proxy_cache[proxy_key] = proxy
|
|
809
810
|
|
|
810
811
|
return proxy
|
|
811
812
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aspyx_service
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.3
|
|
4
4
|
Summary: Aspyx Service framework
|
|
5
5
|
Author-email: Andreas Ernst <andreas.ernst7@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -70,19 +70,23 @@ that lets you deploy, discover and call services with different remoting protoco
|
|
|
70
70
|
|
|
71
71
|
The basic design consists of four different concepts:
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
**Service**
|
|
74
|
+
|
|
74
75
|
defines a group of methods that can be called either locally or remotely.
|
|
75
76
|
These methods represent the functional interface exposed to clients — similar to an interface in traditional programming
|
|
76
77
|
|
|
77
|
-
|
|
78
|
+
**Component**
|
|
79
|
+
|
|
78
80
|
a component bundles one or more services and declares the channels (protocols) used to expose them.
|
|
79
81
|
Think of a component as a deployment unit or module.
|
|
80
82
|
|
|
81
|
-
|
|
83
|
+
**Component Registry**
|
|
84
|
+
|
|
82
85
|
acts as the central directory for managing available components.
|
|
83
86
|
It allows the framework to register, discover, and resolve components and their services.
|
|
84
87
|
|
|
85
|
-
|
|
88
|
+
**Channel**
|
|
89
|
+
|
|
86
90
|
is a pluggable transport layer that defines how service method invocations are transmitted and handled.
|
|
87
91
|
|
|
88
92
|
Let's look at the "interface" layer first.
|
|
@@ -111,7 +115,7 @@ class Module:
|
|
|
111
115
|
|
|
112
116
|
@create()
|
|
113
117
|
def create_registry(self) -> ConsulComponentRegistry:
|
|
114
|
-
return ConsulComponentRegistry(Server.port, "
|
|
118
|
+
return ConsulComponentRegistry(Server.port, Consul(host="localhost", port=8500)) # a consul based registry!
|
|
115
119
|
|
|
116
120
|
environment = Environment(Module)
|
|
117
121
|
service_manager = environment.get(ServiceManager)
|
|
@@ -361,7 +365,7 @@ A so called `URLSelector` is used internally to provide URLs for every single ca
|
|
|
361
365
|
- `FirstURLSelector` always returns the first URL of the list of possible URLs
|
|
362
366
|
- `RoundRobinURLSelector` switches sequentially between all URLs.
|
|
363
367
|
|
|
364
|
-
To customize the behavior, an around advice can be implemented easily:
|
|
368
|
+
To customize the behavior, an `around` advice can be implemented easily:
|
|
365
369
|
|
|
366
370
|
**Example**:
|
|
367
371
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
aspyx_service/__init__.py,sha256=6t24VPrSCG83EAvYlqCKdEcEbyCY3vrSb5GoAx01Ymg,1662
|
|
2
|
+
aspyx_service/channels.py,sha256=p5JIyo7eWyBiR2xQrfsEvq2L89FzeFT1tqKYhvXQbXs,9035
|
|
3
|
+
aspyx_service/healthcheck.py,sha256=8ZPSkAx6ypoYaxDMkJT_MtL2pEN2LcUAishAWPCy-3I,5624
|
|
4
|
+
aspyx_service/registries.py,sha256=JSsD32F8VffZMHyEDuapEWtvmem5SK9kR6bgsFRLFZQ,8002
|
|
5
|
+
aspyx_service/restchannel.py,sha256=Q_7RURjZZW7N2LBLY9BL7qKyS_A62X7yFoGUoE-_0YY,9103
|
|
6
|
+
aspyx_service/serialization.py,sha256=GEgfg1cNSOJ_oe0gEm0ajzugLyUmPiEsp9Qz6Fu4vkA,4207
|
|
7
|
+
aspyx_service/server.py,sha256=PHKx3O90jnxm8dA4z331_51znhU-HlRB2Fa1AikmFXA,7052
|
|
8
|
+
aspyx_service/service.py,sha256=Odl6_nvOOJ2lFjqCLJD8KLPt-sCG55VtQQiKTyNEOPs,26062
|
|
9
|
+
aspyx_service-0.10.3.dist-info/METADATA,sha256=MrWNfy3T2m4G5MyKlV4Spr2yi6N_hE1VLRT3vNJ6Hm0,16955
|
|
10
|
+
aspyx_service-0.10.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
+
aspyx_service-0.10.3.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
|
|
12
|
+
aspyx_service-0.10.3.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
aspyx_service/__init__.py,sha256=6t24VPrSCG83EAvYlqCKdEcEbyCY3vrSb5GoAx01Ymg,1662
|
|
2
|
-
aspyx_service/channels.py,sha256=9EjnY6DV_toqeQNswYZgmAtirXM_TTLGErs6hb1U9yw,8934
|
|
3
|
-
aspyx_service/healthcheck.py,sha256=8ZPSkAx6ypoYaxDMkJT_MtL2pEN2LcUAishAWPCy-3I,5624
|
|
4
|
-
aspyx_service/registries.py,sha256=JSsD32F8VffZMHyEDuapEWtvmem5SK9kR6bgsFRLFZQ,8002
|
|
5
|
-
aspyx_service/restchannel.py,sha256=OVGyKNtORJEFnWsFB5MPkNldVFfJTGuUa4X658_x9Kg,9169
|
|
6
|
-
aspyx_service/serialization.py,sha256=lEr106yiZ0UzVxGTGdiorZSSSNI7vP4C7RyrBJku-U0,4133
|
|
7
|
-
aspyx_service/server.py,sha256=PHKx3O90jnxm8dA4z331_51znhU-HlRB2Fa1AikmFXA,7052
|
|
8
|
-
aspyx_service/service.py,sha256=-hhxRpEtMn-JP57bHNF521zbGMHUy5YaXkbXyiu7dW4,25927
|
|
9
|
-
aspyx_service-0.10.2.dist-info/METADATA,sha256=KEdh30D63sXs26FVOtCph_LF521bBJa7A6Qpc8d-9iQ,16966
|
|
10
|
-
aspyx_service-0.10.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
-
aspyx_service-0.10.2.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
|
|
12
|
-
aspyx_service-0.10.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|