Format: text/x-python
⬇ Download File (4.2 KB)
   1  from __future__ import annotations
   2  
   3  from collections.abc import Callable, Mapping, Sequence
   4  from dataclasses import dataclass
   5  from typing import Any, Generic, TypeVar
   6  
   7  from ..abc import (
   8      ByteReceiveStream,
   9      ByteSendStream,
  10      ByteStream,
  11      Listener,
  12      ObjectReceiveStream,
  13      ObjectSendStream,
  14      ObjectStream,
  15      TaskGroup,
  16  )
  17  
  18  T_Item = TypeVar("T_Item")
  19  T_Stream = TypeVar("T_Stream")
  20  
  21  
  22  @dataclass(eq=False)
  23  class StapledByteStream(ByteStream):
  24      """
  25      Combines two byte streams into a single, bidirectional byte stream.
  26  
  27      Extra attributes will be provided from both streams, with the receive stream
  28      providing the values in case of a conflict.
  29  
  30      :param ByteSendStream send_stream: the sending byte stream
  31      :param ByteReceiveStream receive_stream: the receiving byte stream
  32      """
  33  
  34      send_stream: ByteSendStream
  35      receive_stream: ByteReceiveStream
  36  
  37      async def receive(self, max_bytes: int = 65536) -> bytes:
  38          return await self.receive_stream.receive(max_bytes)
  39  
  40      async def send(self, item: bytes) -> None:
  41          await self.send_stream.send(item)
  42  
  43      async def send_eof(self) -> None:
  44          await self.send_stream.aclose()
  45  
  46      async def aclose(self) -> None:
  47          await self.send_stream.aclose()
  48          await self.receive_stream.aclose()
  49  
  50      @property
  51      def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
  52          return {
  53              **self.send_stream.extra_attributes,
  54              **self.receive_stream.extra_attributes,
  55          }
  56  
  57  
  58  @dataclass(eq=False)
  59  class StapledObjectStream(Generic[T_Item], ObjectStream[T_Item]):
  60      """
  61      Combines two object streams into a single, bidirectional object stream.
  62  
  63      Extra attributes will be provided from both streams, with the receive stream
  64      providing the values in case of a conflict.
  65  
  66      :param ObjectSendStream send_stream: the sending object stream
  67      :param ObjectReceiveStream receive_stream: the receiving object stream
  68      """
  69  
  70      send_stream: ObjectSendStream[T_Item]
  71      receive_stream: ObjectReceiveStream[T_Item]
  72  
  73      async def receive(self) -> T_Item:
  74          return await self.receive_stream.receive()
  75  
  76      async def send(self, item: T_Item) -> None:
  77          await self.send_stream.send(item)
  78  
  79      async def send_eof(self) -> None:
  80          await self.send_stream.aclose()
  81  
  82      async def aclose(self) -> None:
  83          await self.send_stream.aclose()
  84          await self.receive_stream.aclose()
  85  
  86      @property
  87      def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
  88          return {
  89              **self.send_stream.extra_attributes,
  90              **self.receive_stream.extra_attributes,
  91          }
  92  
  93  
  94  @dataclass(eq=False)
  95  class MultiListener(Generic[T_Stream], Listener[T_Stream]):
  96      """
  97      Combines multiple listeners into one, serving connections from all of them at once.
  98  
  99      Any MultiListeners in the given collection of listeners will have their listeners
 100      moved into this one.
 101  
 102      Extra attributes are provided from each listener, with each successive listener
 103      overriding any conflicting attributes from the previous one.
 104  
 105      :param listeners: listeners to serve
 106      :type listeners: Sequence[Listener[T_Stream]]
 107      """
 108  
 109      listeners: Sequence[Listener[T_Stream]]
 110  
 111      def __post_init__(self) -> None:
 112          listeners: list[Listener[T_Stream]] = []
 113          for listener in self.listeners:
 114              if isinstance(listener, MultiListener):
 115                  listeners.extend(listener.listeners)
 116                  del listener.listeners[:]  # type: ignore[attr-defined]
 117              else:
 118                  listeners.append(listener)
 119  
 120          self.listeners = listeners
 121  
 122      async def serve(
 123          self, handler: Callable[[T_Stream], Any], task_group: TaskGroup | None = None
 124      ) -> None:
 125          from .. import create_task_group
 126  
 127          async with create_task_group() as tg:
 128              for listener in self.listeners:
 129                  tg.start_soon(listener.serve, handler, task_group)
 130  
 131      async def aclose(self) -> None:
 132          for listener in self.listeners:
 133              await listener.aclose()
 134  
 135      @property
 136      def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
 137          attributes: dict = {}
 138          for listener in self.listeners:
 139              attributes.update(listener.extra_attributes)
 140  
 141          return attributes
 142