aspyx-service 0.10.7__py3-none-any.whl → 0.11.1__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.

@@ -9,6 +9,7 @@ from typing import get_type_hints, TypeVar, Annotated, Callable, get_origin, get
9
9
  from pydantic import BaseModel
10
10
 
11
11
  from aspyx.reflection import DynamicProxy, Decorators
12
+ from aspyx.util import get_serializer
12
13
 
13
14
  from .channels import HTTPXChannel
14
15
  from .service import channel, ServiceCommunicationException
@@ -130,9 +131,26 @@ class RestChannel(HTTPXChannel):
130
131
  # local class
131
132
 
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
+
133
149
  def __init__(self, type: Type, method : Callable):
134
150
  self.signature = inspect.signature(method)
135
151
 
152
+ type_hints = get_type_hints(method)
153
+
136
154
  param_names = list(self.signature.parameters.keys())
137
155
  param_names.remove("self")
138
156
 
@@ -171,6 +189,7 @@ class RestChannel(HTTPXChannel):
171
189
 
172
190
  if BodyMarker in metadata:
173
191
  self.body_param_name = param_name
192
+ self.body_serializer = get_serializer(type_hints[param_name])
174
193
  param_names.remove(param_name)
175
194
  elif QueryParamMarker in metadata:
176
195
  self.query_param_names.add(param_name)
@@ -197,6 +216,7 @@ class RestChannel(HTTPXChannel):
197
216
  or is_dataclass(typ)
198
217
  ):
199
218
  self.body_param_name = name
219
+ self.body_serializer = get_serializer(type_hints[name])
200
220
  param_names.remove(name)
201
221
  break
202
222
 
@@ -207,7 +227,7 @@ class RestChannel(HTTPXChannel):
207
227
 
208
228
  # return type
209
229
 
210
- self.return_type = get_type_hints(method)['return']
230
+ self.return_type = type_hints['return']
211
231
 
212
232
  # constructor
213
233
 
@@ -241,7 +261,7 @@ class RestChannel(HTTPXChannel):
241
261
  query_params = {k: arguments[k] for k in call.query_param_names if k in arguments}
242
262
  body = {}
243
263
  if call.body_param_name is not None:
244
- body = self.to_dict(arguments.get(call.body_param_name))
264
+ body = call.body_serializer(arguments.get(call.body_param_name))#self.to_dict(arguments.get(call.body_param_name))
245
265
 
246
266
  # call
247
267
 
@@ -273,7 +293,7 @@ class RestChannel(HTTPXChannel):
273
293
  query_params = {k: arguments[k] for k in call.query_param_names if k in arguments}
274
294
  body = {}
275
295
  if call.body_param_name is not None:
276
- body = self.to_dict(arguments.get(call.body_param_name))
296
+ body = call.body_serializer(arguments.get(call.body_param_name))#self.to_dict(arguments.get(call.body_param_name))
277
297
 
278
298
  # call
279
299
 
aspyx_service/server.py CHANGED
@@ -14,16 +14,17 @@ import msgpack
14
14
  import uvicorn
15
15
 
16
16
  from fastapi import FastAPI, APIRouter, Request as HttpRequest, Response as HttpResponse, HTTPException
17
-
17
+ from fastapi.datastructures import DefaultPlaceholder, Default
18
18
 
19
19
  from fastapi.responses import JSONResponse
20
20
  from starlette.middleware.base import BaseHTTPMiddleware
21
21
 
22
- from aspyx.di import Environment, injectable, on_init, inject_environment, on_destroy
22
+ from aspyx.di import Environment, on_init, inject_environment, on_destroy
23
23
  from aspyx.reflection import TypeDescriptor, Decorators
24
- from aspyx.util import get_deserializer, get_serializer
24
+ from aspyx.util import get_deserializer, get_serializer, CopyOnWriteCache
25
25
 
26
- from .service import ComponentRegistry
26
+ from .protobuf import ProtobufManager
27
+ from .service import ComponentRegistry, ServiceDescriptor
27
28
  from .healthcheck import HealthCheckManager
28
29
 
29
30
  from .service import Server, ServiceManager
@@ -172,10 +173,11 @@ class FastAPIServer(Server):
172
173
 
173
174
  # constructor
174
175
 
175
- def __init__(self, fast_api: FastAPI, service_manager: ServiceManager, component_registry: ComponentRegistry):
176
+ def __init__(self, fast_api: FastAPI, service_manager: ServiceManager, component_registry: ComponentRegistry, protobuf_manager: ProtobufManager):
176
177
  super().__init__()
177
178
 
178
179
  self.environment : Optional[Environment] = None
180
+ self.protobuf_manager = protobuf_manager
179
181
  self.service_manager = service_manager
180
182
  self.component_registry = component_registry
181
183
 
@@ -190,7 +192,7 @@ class FastAPIServer(Server):
190
192
 
191
193
  # cache
192
194
 
193
- self.deserializers: dict[str, list[Callable]] = {}
195
+ self.deserializers = CopyOnWriteCache[str, list[Callable]]()
194
196
 
195
197
  # that's the overall dispatcher
196
198
 
@@ -213,6 +215,7 @@ class FastAPIServer(Server):
213
215
  self.add_routes()
214
216
  self.fast_api.include_router(self.router)
215
217
 
218
+ # TODO: trace routes
216
219
  #for route in self.fast_api.routes:
217
220
  # print(f"{route.name}: {route.path} [{route.methods}]")
218
221
 
@@ -325,18 +328,16 @@ class FastAPIServer(Server):
325
328
  self.thread.start()
326
329
 
327
330
  def get_deserializers(self, service: Type, method):
328
- deserializers = self.deserializers.get(method, None)
331
+ deserializers = self.deserializers.get(method)
329
332
  if deserializers is None:
330
333
  descriptor = TypeDescriptor.for_type(service).get_method(method.__name__)
331
334
 
332
335
  deserializers = [get_deserializer(type) for type in descriptor.param_types]
333
- self.deserializers[method] = deserializers
336
+ self.deserializers.put(method, deserializers)
334
337
 
335
338
  return deserializers
336
339
 
337
340
  def deserialize_args(self, args: list[Any], type: Type, method: Callable) -> list:
338
- #args = list(request.args)
339
-
340
341
  deserializers = self.get_deserializers(type, method)
341
342
 
342
343
  for i, arg in enumerate(args):
@@ -344,68 +345,110 @@ class FastAPIServer(Server):
344
345
 
345
346
  return args
346
347
 
347
- async def invoke(self, http_request: HttpRequest):
348
- content_type = http_request.headers.get("content-type", "")
348
+ def get_descriptor_and_method(self, method_name: str) -> typing.Tuple[ServiceDescriptor, Callable]:
349
+ parts = method_name.split(":")
349
350
 
350
- content = "json"
351
- if "application/msgpack" in content_type:
352
- content = "msgpack"
353
- raw_data = await http_request.body()
354
- data = msgpack.unpackb(raw_data, raw=False)
355
- elif "application/json" in content_type:
356
- data = await http_request.json()
357
- else:
358
- return HttpResponse(
359
- content="Unsupported Content-Type",
360
- status_code=415,
361
- media_type="text/plain"
362
- )
351
+ # component = parts[0]
352
+ service_name = parts[1]
353
+ method_name = parts[2]
363
354
 
364
- request = data
355
+ service_descriptor = typing.cast(ServiceDescriptor, ServiceManager.descriptors_by_name[service_name])
356
+ service = self.service_manager.get_service(service_descriptor.type, preferred_channel="local")
365
357
 
366
- if content == "json":
367
- return await self.dispatch(http_request, request)
368
- else:
369
- return HttpResponse(
370
- content=msgpack.packb(await self.dispatch(http_request, request), use_bin_type=True),
371
- media_type="application/msgpack"
372
- )
358
+ return service_descriptor, getattr(service, method_name)
373
359
 
374
- async def dispatch(self, http_request: HttpRequest, request: dict) :
375
- ServiceManager.logger.debug("dispatch request %s", request["method"])
360
+ async def invoke_json(self, http_request: HttpRequest):
361
+ data = await http_request.json()
362
+ service_descriptor, method = self.get_descriptor_and_method(data["method"])
363
+ args = self.deserialize_args(data["args"], service_descriptor.type, method)
376
364
 
377
- # <comp>:<service>:<method>
365
+ try:
366
+ result = await self.dispatch(service_descriptor, method, args)
378
367
 
379
- parts = request["method"].split(":")
368
+ return Response(result=result, exception=None).model_dump()
369
+ except Exception as e:
370
+ return Response(result=None, exception=str(e)).model_dump()
380
371
 
381
- #component = parts[0]
382
- service_name = parts[1]
383
- method_name = parts[2]
372
+ async def invoke_msgpack(self, http_request: HttpRequest):
373
+ data = msgpack.unpackb(await http_request.body(), raw=False)
374
+ service_descriptor, method = self.get_descriptor_and_method(data["method"])
375
+ args = self.deserialize_args(data["args"], service_descriptor.type, method)
384
376
 
385
- service_descriptor = ServiceManager.descriptors_by_name[service_name]
386
- service = self.service_manager.get_service(service_descriptor.type, preferred_channel="local")
377
+ try:
378
+ response = Response(result=await self.dispatch(service_descriptor, method, args), exception=None).model_dump()
379
+ except Exception as e:
380
+ response = Response(result=None, exception=str(e)).model_dump()
381
+
382
+ return HttpResponse(
383
+ content=msgpack.packb(response, use_bin_type=True),
384
+ media_type="application/msgpack"
385
+ )
386
+
387
+ async def invoke_protobuf(self, http_request: HttpRequest):
388
+ service_descriptor, method = self.get_descriptor_and_method(http_request.headers.get("x-rpc-method"))
387
389
 
388
- method = getattr(service, method_name)
390
+ data = await http_request.body()
389
391
 
390
- args = self.deserialize_args(request["args"], service_descriptor.type, method)
392
+ # create message
393
+
394
+ request = self.protobuf_manager.get_request_message(service_descriptor.type, method)()
395
+ request.ParseFromString(data)
396
+
397
+ # and parse
398
+
399
+ args = self.protobuf_manager.create_deserializer(request.DESCRIPTOR, method).deserialize(request)
400
+
401
+ response_type = self.protobuf_manager.get_response_message(service_descriptor.type,method)
402
+ result_serializer = self.protobuf_manager.create_result_serializer(response_type, method)
391
403
  try:
392
- if inspect.iscoroutinefunction(method):
393
- result = await method(*args)
394
- else:
395
- result = method(*args)
404
+ result = await self.dispatch(service_descriptor, method, args)
396
405
 
397
- return Response(result=result, exception=None).model_dump()
406
+ result_message = result_serializer.serialize_result(result, None)
398
407
 
399
- except HTTPException as e:
400
- raise
408
+ return HttpResponse(
409
+ content=result_message.SerializeToString(),
410
+ media_type="application/x-protobuf"
411
+ )
401
412
 
402
413
  except Exception as e:
403
- return Response(result=None, exception=str(e)).model_dump()
414
+ result_message = result_serializer.serialize_result(None, str(e))
415
+
416
+ return HttpResponse(
417
+ content=result_message.SerializeToString(),
418
+ media_type="application/x-protobuf"
419
+ )
420
+
421
+ async def invoke(self, http_request: HttpRequest):
422
+ content_type = http_request.headers.get("content-type", "")
423
+
424
+ if content_type == "application/x-protobuf":
425
+ return await self.invoke_protobuf(http_request)
426
+
427
+ elif content_type == "application/msgpack":
428
+ return await self.invoke_msgpack(http_request)
429
+
430
+ elif content_type == "application/json":
431
+ return await self.invoke_json(http_request)
432
+
433
+ else:
434
+ return HttpResponse(
435
+ content="Unsupported Content-Type",
436
+ status_code=415,
437
+ media_type="text/plain"
438
+ )
439
+
440
+ async def dispatch(self, service_descriptor: ServiceDescriptor, method: Callable, args: list[Any]) :
441
+ #ServiceManager.logger.debug("dispatch request %s.%s", service_descriptor, method.__name__)
442
+
443
+ if inspect.iscoroutinefunction(method):
444
+ return await method(*args)
445
+ else:
446
+ return method(*args)
404
447
 
405
448
  # override
406
449
 
407
- def route(self, url: str, callable: Callable):
408
- self.router.get(url)(callable)
450
+ def add_route(self, path: str, endpoint: Callable, methods: list[str], response_class: typing.Union[Type[Response], DefaultPlaceholder] = Default(JSONResponse)):
451
+ self.router.add_api_route(path=path, endpoint=endpoint, methods=methods, response_class=response_class)
409
452
 
410
453
  def route_health(self, url: str, callable: Callable):
411
454
  async def get_health_response():
aspyx_service/service.py CHANGED
@@ -7,17 +7,23 @@ import re
7
7
  import socket
8
8
  import logging
9
9
  import threading
10
+ import typing
10
11
  from abc import abstractmethod, ABC
11
12
  from dataclasses import dataclass
12
13
  from enum import Enum, auto
13
14
 
14
15
  from typing import Type, TypeVar, Generic, Callable, Optional, cast
15
16
 
17
+ from fastapi.datastructures import DefaultPlaceholder, Default
18
+ from httpx import Response
19
+ from starlette.responses import JSONResponse, PlainTextResponse
20
+
16
21
  from aspyx.di import injectable, Environment, Providers, ClassInstanceProvider, inject_environment, order, \
17
22
  Lifecycle, LifecycleCallable, InstanceProvider
18
23
  from aspyx.di.aop.aop import ClassAspectTarget
19
24
  from aspyx.reflection import Decorators, DynamicProxy, DecoratorDescriptor, TypeDescriptor
20
25
  from aspyx.util import StringBuilder
26
+
21
27
  from .healthcheck import HealthCheckManager, HealthStatus
22
28
 
23
29
  T = TypeVar("T")
@@ -41,7 +47,7 @@ class ComponentStatus(Enum):
41
47
 
42
48
  class Server(ABC):
43
49
  """
44
- A server is a central entity that boots a main module and initializes the ServiceManager.
50
+ A server is a central class that boots a main module and initializes the ServiceManager.
45
51
  It also is the place where http servers get initialized.
46
52
  """
47
53
  port = 0
@@ -50,6 +56,7 @@ class Server(ABC):
50
56
 
51
57
  def __init__(self):
52
58
  self.environment : Optional[Environment] = None
59
+ self.instance = self
53
60
 
54
61
  # public
55
62
 
@@ -57,7 +64,7 @@ class Server(ABC):
57
64
  return self.environment.get(type)
58
65
 
59
66
  @abstractmethod
60
- def route(self, url : str, callable: Callable):
67
+ def add_route(self, path : str, endpoint : Callable, methods : list[str], response_class : typing.Union[Type[Response], DefaultPlaceholder] = Default(JSONResponse)):
61
68
  pass
62
69
 
63
70
  @abstractmethod
@@ -557,7 +564,7 @@ class ComponentRegistry:
557
564
 
558
565
 
559
566
  @injectable()
560
- class ChannelManager:
567
+ class ChannelFactory:
561
568
  """
562
569
  Internal factory for channels.
563
570
  """
@@ -567,12 +574,12 @@ class ChannelManager:
567
574
  def register_channel(cls, channel: str, type: Type):
568
575
  ServiceManager.logger.info("register channel %s", channel)
569
576
 
570
- ChannelManager.factories[channel] = type
577
+ ChannelFactory.factories[channel] = type
571
578
 
572
579
  # constructor
573
580
 
574
581
  def __init__(self):
575
- self.environment = None
582
+ self.environment : Optional[Environment] = None
576
583
 
577
584
  # lifecycle hooks
578
585
 
@@ -582,6 +589,12 @@ class ChannelManager:
582
589
 
583
590
  # public
584
591
 
592
+ def prepare_channel(self, server: Server, channel: str, component_descriptor: ComponentDescriptor):
593
+ type = self.factories[channel]
594
+
595
+ if getattr(type, "prepare", None) is not None:
596
+ getattr(type, "prepare", None)(server, component_descriptor)
597
+
585
598
  def make(self, name: str, descriptor: ComponentDescriptor, address: ChannelInstances) -> Channel:
586
599
  ServiceManager.logger.info("create channel %s: %s", name, self.factories.get(name).__name__)
587
600
 
@@ -603,7 +616,7 @@ def channel(name: str):
603
616
 
604
617
  Providers.register(ClassInstanceProvider(cls, False, "request"))
605
618
 
606
- ChannelManager.register_channel(name, cls)
619
+ ChannelFactory.register_channel(name, cls)
607
620
 
608
621
  return cls
609
622
 
@@ -649,20 +662,24 @@ class ServiceManager:
649
662
  def register_component(cls, component_type: Type, services: list[Type]):
650
663
  component_descriptor = ComponentDescriptor(component_type, services)
651
664
 
665
+ setattr(component_type, "__descriptor__", component_descriptor)
666
+
652
667
  cls.logger.info("register component %s", component_descriptor.name)
653
668
 
654
669
  ServiceManager.descriptors[component_type] = component_descriptor
655
670
  ServiceManager.descriptors_by_name[component_descriptor.name] = component_descriptor
656
671
 
657
672
  for component_service in component_descriptor.services:
673
+ setattr(component_service.type, "__descriptor__", component_service)
674
+
658
675
  ServiceManager.descriptors[component_service.type] = component_service
659
676
  ServiceManager.descriptors_by_name[component_service.name] = component_service
660
677
 
661
678
  # constructor
662
679
 
663
- def __init__(self, component_registry: ComponentRegistry, channel_manager: ChannelManager):
680
+ def __init__(self, component_registry: ComponentRegistry, channel_factory: ChannelFactory):
664
681
  self.component_registry = component_registry
665
- self.channel_manager = channel_manager
682
+ self.channel_factory = channel_factory
666
683
  self.environment : Optional[Environment] = None
667
684
  self.preferred_channel = ""
668
685
 
@@ -686,7 +703,7 @@ class ServiceManager:
686
703
  def get_instance(self, type: Type[T]) -> T:
687
704
  instance = self.instances.get(type)
688
705
  if instance is None:
689
- ServiceManager.logger.info("create implementation %s", type.__name__)
706
+ ServiceManager.logger.debug("create implementation %s", type.__name__)
690
707
 
691
708
  instance = self.environment.get(type)
692
709
  self.instances[type] = instance
@@ -695,9 +712,16 @@ class ServiceManager:
695
712
 
696
713
  # lifecycle
697
714
 
715
+
698
716
  def startup(self, server: Server) -> None:
699
717
  self.logger.info("startup on port %s", server.port)
700
718
 
719
+ # add some introspection endpoints
720
+
721
+ server.add_route(path="/report", endpoint=lambda: self.report(), methods=["GET"], response_class=PlainTextResponse)
722
+
723
+ # boot components
724
+
701
725
  for descriptor in self.descriptors.values():
702
726
  if descriptor.is_component():
703
727
  # register local address
@@ -720,8 +744,6 @@ class ServiceManager:
720
744
 
721
745
  self.component_registry.register(descriptor.get_component_descriptor(), [ChannelAddress("local", "")])
722
746
 
723
- #health_name = next((decorator.args[0] for decorator in Decorators.get(descriptor.type) if decorator.decorator is health), None)
724
-
725
747
  # startup
726
748
 
727
749
  instance.startup()
@@ -733,9 +755,10 @@ class ServiceManager:
733
755
 
734
756
  # register addresses
735
757
 
736
- self.component_registry.register(descriptor.get_component_descriptor(), descriptor.addresses)
758
+ for address in descriptor.addresses:
759
+ self.channel_factory.prepare_channel(server, address.channel, descriptor.get_component_descriptor())
737
760
 
738
- print(self.report())
761
+ self.component_registry.register(descriptor.get_component_descriptor(), descriptor.addresses)
739
762
 
740
763
  def shutdown(self):
741
764
  self.logger.info("shutdown")
@@ -818,7 +841,7 @@ class ServiceManager:
818
841
  if channel_instance is None:
819
842
  # create channel
820
843
 
821
- channel_instance = self.channel_manager.make(address.channel, component_descriptor, address)
844
+ channel_instance = self.channel_factory.make(address.channel, component_descriptor, address)
822
845
 
823
846
  # cache
824
847
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aspyx_service
3
- Version: 0.10.7
3
+ Version: 0.11.1
4
4
  Summary: Aspyx Service framework
5
5
  Author-email: Andreas Ernst <andreas.ernst7@gmail.com>
6
6
  License: MIT License
@@ -26,10 +26,11 @@ License: MIT License
26
26
  SOFTWARE.
27
27
  License-File: LICENSE
28
28
  Requires-Python: >=3.9
29
- Requires-Dist: aspyx>=1.6.0
29
+ Requires-Dist: aspyx>=1.7.0
30
30
  Requires-Dist: fastapi~=0.115.13
31
31
  Requires-Dist: httpx~=0.28.1
32
32
  Requires-Dist: msgpack~=1.1.1
33
+ Requires-Dist: protobuf~=5.29.4
33
34
  Requires-Dist: python-consul2~=0.1.5
34
35
  Requires-Dist: uvicorn[standard]
35
36
  Description-Content-Type: text/markdown
@@ -113,9 +114,6 @@ After booting the DI infrastructure with a main module we could already call a s
113
114
  ```python
114
115
  @module(imports=[ServiceModule])
115
116
  class Module:
116
- def __init__(self):
117
- pass
118
-
119
117
  @create()
120
118
  def create_registry(self) -> ConsulComponentRegistry:
121
119
  return ConsulComponentRegistry(Server.port, Consul(host="localhost", port=8500)) # a consul based registry!
@@ -136,11 +134,6 @@ As we can also host implementations, lets look at this side as well:
136
134
  ```python
137
135
  @implementation()
138
136
  class TestComponentImpl(AbstractComponent, TestComponent):
139
- # constructor
140
-
141
- def __init__(self):
142
- super().__init__()
143
-
144
137
  # implement Component
145
138
 
146
139
  def get_addresses(self, port: int) -> list[ChannelAddress]:
@@ -148,9 +141,6 @@ class TestComponentImpl(AbstractComponent, TestComponent):
148
141
 
149
142
  @implementation()
150
143
  class TestServiceImpl(TestService):
151
- def __init__(self):
152
- pass
153
-
154
144
  def hello(self, message: str) -> str:
155
145
  return f"hello {message}"
156
146
  ```
@@ -262,8 +252,7 @@ Service implementations implement the corresponding interface and are decorated
262
252
  ```python
263
253
  @implementation()
264
254
  class TestServiceImpl(TestService):
265
- def __init__(self):
266
- pass
255
+ pass
267
256
  ```
268
257
 
269
258
  The constructor is required since the instances are managed by the DI framework.
@@ -273,11 +262,6 @@ Component implementations derive from the interface and the abstract base class
273
262
  ```python
274
263
  @implementation()
275
264
  class TestComponentImpl(AbstractComponent, TestComponent):
276
- # constructor
277
-
278
- def __init__(self):
279
- super().__init__()
280
-
281
265
  # implement Component
282
266
 
283
267
  def get_addresses(self, port: int) -> list[ChannelAddress]:
@@ -312,7 +296,9 @@ For this purpose injectable classes can be decorated with `@health_checks()` tha
312
296
  @injectable()
313
297
  class Checks:
314
298
  def __init__(self):
315
- pass
299
+ pass # normally, we would inject stuff here
300
+
301
+ # checks
316
302
 
317
303
  @health_check(fail_if_slower_than=1)
318
304
  def check_performance(self, result: HealthCheckManager.Result):
@@ -381,6 +367,8 @@ Several channels are implemented:
381
367
  channel that posts generic `Request` objects via a `invoke` POST-call
382
368
  - `dispatch-msgpack`
383
369
  channel that posts generic `Request` objects via a `invoke` POST-call after packing the json with msgpack
370
+ - `dispatch-protobuf`
371
+ channel that posts parameters via a `invoke` POST-call after packing the arguments with protobuf
384
372
  - `rest`
385
373
  channel that executes regular rest-calls as defined by a couple of decorators.
386
374
 
@@ -400,14 +388,6 @@ To customize the behavior, an `around` advice can be implemented easily:
400
388
  ```python
401
389
  @advice
402
390
  class ChannelAdvice:
403
- def __init__(self):
404
- pass
405
-
406
- @advice
407
- class ChannelAdvice:
408
- def __init__(self):
409
- pass
410
-
411
391
  @around(methods().named("customize").of_type(Channel))
412
392
  def customize_channel(self, invocation: Invocation):
413
393
  channel = cast(Channel, invocation.args[0])
@@ -425,6 +405,7 @@ The avg response times - on a local server - where all below 1ms per call.
425
405
  - rest calls are the slowest ( about 0.7ms )
426
406
  - dispatching-json 20% faster
427
407
  - dispatching-msgpack 30% faster
408
+ - dispatching protobuf
428
409
 
429
410
  The biggest advantage of the dispatching flavors is, that you don't have to worry about the additional decorators!
430
411
 
@@ -455,6 +436,11 @@ Additional annotations are
455
436
  - `Body` the post body
456
437
  - `QueryParam`marked for query params
457
438
 
439
+ You can skip the annotations, assuming the following heuristic:
440
+
441
+ - if no body is marked it will pick the first parameter which is a dataclass or a pydantic model
442
+ - all parameters which are not in the path or equal to the body are assumed to be query params.
443
+
458
444
  ### Intercepting calls
459
445
 
460
446
  The client side HTTP calling is done with `httpx` instances of type `Httpx.Client` or `Httpx.AsyncClient`.
@@ -500,7 +486,7 @@ The required - `FastAPI` - infrastructure to expose those services requires:
500
486
  - and a final `boot` call with the root module, which will return an `Environment`
501
487
 
502
488
  ```python
503
- fast_api = FastAPI() # so you can run it with uvivorn from command-line
489
+ fast_api = FastAPI() # so you can run it with uvicorn from command-line
504
490
 
505
491
  @module(imports=[ServiceModule])
506
492
  class Module:
@@ -512,7 +498,7 @@ class Module:
512
498
  return FastAPIServer(fastapi, service_manager, component_registry)
513
499
 
514
500
 
515
- environment = FastAPIServer.boot(Moudle, host="0.0.0.0", port=8000)
501
+ environment = FastAPIServer.boot(Module, host="0.0.0.0", port=8000)
516
502
  ```
517
503
 
518
504
  This setup will also expose all service interfaces decorated with the corresponding http decorators!
@@ -559,6 +545,10 @@ class FancyChannel(Channel):
559
545
 
560
546
  - first release version
561
547
 
548
+ **0.11.0**
549
+
550
+ - added protobuf support
551
+
562
552
 
563
553
 
564
554
 
@@ -0,0 +1,14 @@
1
+ aspyx_service/__init__.py,sha256=Mzt6pBhME_qDij2timEZT0emTTXRues_xXu3muhk3Jc,2642
2
+ aspyx_service/authorization.py,sha256=0B1xb0WrRaj2rcGTHVUhh6i8aA0sy7BmpYA18xI9LQA,3833
3
+ aspyx_service/channels.py,sha256=ujJnyWijNNNsl_kJu39bFV2MDq8OjBGOs7XXDWdpx9w,15314
4
+ aspyx_service/healthcheck.py,sha256=XiQx1T0DP0kcCyK_sYBuE-JHs5N285HotLVycFCgzBU,5612
5
+ aspyx_service/protobuf.py,sha256=w2wZymTSObGhgevIRZJ9kRxiEel8f6BycMPQiTYNMzI,39595
6
+ aspyx_service/registries.py,sha256=bnTjKb40fbZXA52E2lDSEzCWI5_NBKZzQjc8ffufB5g,8039
7
+ aspyx_service/restchannel.py,sha256=aCpNCvv1eIRa9IJW8Os4bQQErZKrEeyID65QhnD9QJw,9134
8
+ aspyx_service/server.py,sha256=yI_croAT77PUh2_z1-EGklgzxjMvuUtHomckN3piaBo,15794
9
+ aspyx_service/service.py,sha256=gOX5rbOOLJ-cSIvxa_5lqo1DzcPmDoJCBbMP-ZYKYxs,27972
10
+ aspyx_service/session.py,sha256=HjGpnmwdislc8Ur6pQbSMi2K-lvTsb9_XyO80zupiF8,3713
11
+ aspyx_service-0.11.1.dist-info/METADATA,sha256=nYCeGXZYBRnaEIFYNJYgIY_b5p0qEIOHZI30hwEUwr4,18127
12
+ aspyx_service-0.11.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
13
+ aspyx_service-0.11.1.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
14
+ aspyx_service-0.11.1.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- aspyx_service/__init__.py,sha256=OWJoScdDVK1NTs9cIgImShgEdJb8TZHLBQjS11rA0Yo,2564
2
- aspyx_service/authorization.py,sha256=0B1xb0WrRaj2rcGTHVUhh6i8aA0sy7BmpYA18xI9LQA,3833
3
- aspyx_service/channels.py,sha256=u1afqUfcmVgLxXDXC2BYHH-dMozLcmVROuPRpypSwr8,16397
4
- aspyx_service/healthcheck.py,sha256=vjfY7s5kd5mRJynVpvAJ4BvVF7QY1xrvj94Y-m041LQ,5615
5
- aspyx_service/registries.py,sha256=bnTjKb40fbZXA52E2lDSEzCWI5_NBKZzQjc8ffufB5g,8039
6
- aspyx_service/restchannel.py,sha256=0Xb8grEE8Dyx3g3ENl78DDMKa2WGjIKIPgOrpw5p9ak,8470
7
- aspyx_service/server.py,sha256=75B1l__GknjwKX2jGIoR4YUTaaFBEVQ_qfHW2a3cy4U,13354
8
- aspyx_service/service.py,sha256=drETAZasbYJZisnmbhAqW0-mHghJ3IWyPaU-7etxvBI,27003
9
- aspyx_service/session.py,sha256=HjGpnmwdislc8Ur6pQbSMi2K-lvTsb9_XyO80zupiF8,3713
10
- aspyx_service-0.10.7.dist-info/METADATA,sha256=mZ2ZsGGqncY4y4pGpeaOy-cDIQu7OHwRWjW09T1jzwM,17946
11
- aspyx_service-0.10.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
12
- aspyx_service-0.10.7.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
13
- aspyx_service-0.10.7.dist-info/RECORD,,