from __future__ import annotations from typing import Callable, Generic, TypeVar T = TypeVar("T") def _load_msgpack(): try: import msgpack except ImportError as exc: raise RuntimeError("Diffulex ZMQ serving requires msgpack. Install the project dependencies first.") from exc return msgpack def _load_zmq(): try: import zmq except ImportError as exc: raise RuntimeError("Diffulex ZMQ serving requires pyzmq. Install the project dependencies first.") from exc return zmq def _load_zmq_asyncio(): try: import zmq.asyncio except ImportError as exc: raise RuntimeError("Diffulex ZMQ serving requires pyzmq. Install the project dependencies first.") from exc return zmq.asyncio class ZmqPushQueue(Generic[T]): def __init__(self, addr: str, *, create: bool, encoder: Callable[[T], dict]): zmq = _load_zmq() self._msgpack = _load_msgpack() self.context = zmq.Context() self.socket = self.context.socket(zmq.PUSH) self.socket.setsockopt(zmq.LINGER, 0) self.socket.bind(addr) if create else self.socket.connect(addr) self.encoder = encoder def put(self, obj: T) -> None: event = self._msgpack.packb(self.encoder(obj), use_bin_type=True) self.socket.send(event, copy=False) def stop(self) -> None: self.socket.close() self.context.term() class ZmqPullQueue(Generic[T]): def __init__(self, addr: str, *, create: bool, decoder: Callable[[dict], T]): zmq = _load_zmq() self._msgpack = _load_msgpack() self.context = zmq.Context() self.socket = self.context.socket(zmq.PULL) self.socket.setsockopt(zmq.LINGER, 0) self.socket.bind(addr) if create else self.socket.connect(addr) self.decoder = decoder def get(self) -> T: event = self.socket.recv() return self.decoder(self._msgpack.unpackb(event, raw=False)) def empty(self) -> bool: return self.socket.poll(timeout=0) == 0 def stop(self) -> None: self.socket.close() self.context.term() class ZmqAsyncPushQueue(Generic[T]): def __init__(self, addr: str, *, create: bool, encoder: Callable[[T], dict]): zmq_asyncio = _load_zmq_asyncio() self._msgpack = _load_msgpack() self.context = zmq_asyncio.Context() zmq = _load_zmq() self.socket = self.context.socket(zmq.PUSH) self.socket.setsockopt(zmq.LINGER, 0) self.socket.bind(addr) if create else self.socket.connect(addr) self.encoder = encoder async def put(self, obj: T) -> None: event = self._msgpack.packb(self.encoder(obj), use_bin_type=True) await self.socket.send(event, copy=False) def stop(self) -> None: self.socket.close() self.context.term() class ZmqAsyncPullQueue(Generic[T]): def __init__(self, addr: str, *, create: bool, decoder: Callable[[dict], T]): zmq_asyncio = _load_zmq_asyncio() self._msgpack = _load_msgpack() self.context = zmq_asyncio.Context() zmq = _load_zmq() self.socket = self.context.socket(zmq.PULL) self.socket.setsockopt(zmq.LINGER, 0) self.socket.bind(addr) if create else self.socket.connect(addr) self.decoder = decoder async def get(self) -> T: event = await self.socket.recv() return self.decoder(self._msgpack.unpackb(event, raw=False)) def stop(self) -> None: self.socket.close() self.context.term()