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 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
- if self.client is None:
107
- self.client = self.make_client()
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 self.client
111
+ return client
110
112
 
111
113
  def get_async_client(self) -> AsyncClient:
112
- if self.async_client is None:
113
- self.async_client = self.make_async_client()
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 self.async_client
120
+ return async_client
116
121
 
117
122
  def make_client(self) -> Client:
118
123
  return Client() # base_url=url
@@ -249,16 +249,15 @@ class RestChannel(HTTPXChannel):
249
249
  try:
250
250
  result = None
251
251
  if call.type == "get":
252
- result = await self.get_client().get(self.get_url() + url, params=query_params, timeout=self.timeout).json()
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.get_client().put(self.get_url() + url, params=query_params, timeout=self.timeout).json()
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.get_client().delete(self.get_url() + url, params=query_params, timeout=self.timeout).json()
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.get_client().post(self.get_url() + url, params=query_params, json=body,
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).json()
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).json()
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).json()
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).json()
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
 
@@ -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
- key = TypeAndChannel(type=component_descriptor.type, channel=preferred_channel)
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(key, None)
775
+ proxy = self.proxy_cache.get(proxy_key, None)
775
776
  if proxy is None:
776
- channel_instance = self.channel_cache.get(key, None)
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
- key = TypeAndChannel(type=component_descriptor.type, channel=address.channel)
790
+ channel_key = TypeAndChannel(type=component_descriptor.type, channel=address.channel)
790
791
 
791
- channel_instance = self.channel_cache.get(key, None)
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[key] = channel_instance
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[key] = proxy
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.2
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
- !!! info "Service"
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
- !!! info "Component"
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
- !!! info "Component Registry "
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
- !!! info "Channel"
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, "http://localhost:8500") # a consul based registry!
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,,