async def init_event_stream ( event_stream : AsyncIOEventEmitter, puppet : PuppetStub, ) -> None: """doc""" async for response in puppet.event(): # print(response) if response is not None: event_type = EventType(response.type).name payload = response.payload # print(event_type, payload) event_stream.emit(event_type, payload)
def peerconn_oniceconnectionstatechange(): state_change = AsyncIOEventEmitter() @state_change.once("failed") async def on_failed(error=None): log.warning( "\n IceConnectionState failed with error %s " "\n Closing connections to peer id %s", error, peerId) self.connection.emit( ConnectionEventType.Error, ConnectionError( f"Negotiation of connection to {peerId} failed.")) await self.connection.close() @state_change.once("closed") def on_closed(): log.info("iceConnectionState is closed, " f"closing connections to {peerId}") self.connection.emit( ConnectionEventType.Error, ConnectionError(f"Connection to {peerId} closed.")) self.connection.close() @state_change.once("disconnected") def on_disconnected(): log.info("iceConnectionState is disconnected, " f"closing connections to {peerId}") self.connection.emit( ConnectionEventType.Error, ConnectionError(f"Connection to {peerId} disconnected.")) self.connection.close() @state_change.once("completed") def on_completed(): log.debug('iceConnectionState completed for peer id %s', peerId) # this function needs to be implemented as in PeerJS # https://github.com/peers/peerjs/blob/5e36ba17be02a9af7c171399f7737d8ccb38fbbe/lib/negotiator.ts#L119 # when the "icecandidate" # event handling issue above is resolved # peerConnection.remove_listener("icecandidate", # peerconn_onicecandidate) pass log.debug('iceConnectionState event: %s', peerConnection.iceConnectionState) # forward connection stage change event to local handlers state_change.emit(peerConnection.iceConnectionState) # notify higher level connection listeners self.connection.emit(ConnectionEventType.IceStateChanged, peerConnection.iceConnectionState)
async def test_asyncio_once_emit(event_loop): """Test that EventEmitters also wrap coroutines when using once """ ee = AsyncIOEventEmitter(loop=event_loop) should_call = Future(loop=event_loop) @ee.once('event') async def event_handler(): should_call.set_result(True) ee.emit('event') result = await wait_for(should_call, 0.1) assert result == True
async def test_asyncio_emit(event_loop): """Test that AsyncIOEventEmitter can handle wrapping coroutines """ ee = AsyncIOEventEmitter(loop=event_loop) should_call = Future(loop=event_loop) @ee.on('event') async def event_handler(): should_call.set_result(True) ee.emit('event') result = await wait_for(should_call, 0.1) assert result is True
async def test_sync_error(event_loop): """Test that regular functions have the same error handling as coroutines """ ee = AsyncIOEventEmitter(loop=event_loop) should_call = Future(loop=event_loop) @ee.on('event') def sync_handler(): raise PyeeTestError() @ee.on('error') def handle_error(exc): should_call.set_result(exc) ee.emit('event') result = await wait_for(should_call, 0.1) assert isinstance(result, PyeeTestError)
class MockWebsocket(): def __init__(self): self.events = AsyncIOEventEmitter() self.saved_items = [] self.emitted_items = [] def on(self, *args, **kwargs): self.events.on(*args, **kwargs) def once(self, *args, **kwargs): self.events.once(*args, **kwargs) def _emit(self, event, *args, **kwargs): # save published items for testing self.emitted_items += [{ 'time': int(round(time.time() * 1000)), 'data': { 'event': event, 'args': args, 'kwargs': kwargs } }] self.events.emit(event, *args, **kwargs) def remove_all_listeners(self, *args, **kwargs): self.events.remove_all_listeners(*args, **kwargs) def cancel_order(self, *args, **kawargs): pass def submit_order(self, *args, **kawargs): pass def get_emitted_items(self): return self.emitted_items def get_last_emitted_item(self): return self.emitted_items[-1:][0] def get_emitted_items_count(self): return len(self.emitted_items)
async def test_asyncio_error(event_loop): """Test that AsyncIOEventEmitter can handle errors when wrapping coroutines """ ee = AsyncIOEventEmitter(loop=event_loop) should_call = Future(loop=event_loop) @ee.on('event') async def event_handler(): raise PyeeTestError() @ee.on('error') def handle_error(exc): should_call.set_result(exc) ee.emit('event') result = await wait_for(should_call, 0.1) assert isinstance(result, PyeeTestError)
async def test_asyncio_cancellation(event_loop): """Test that AsyncIOEventEmitter can handle Future cancellations""" cancel_me = Future(loop=event_loop) should_not_call = Future(loop=event_loop) ee = AsyncIOEventEmitter(loop=event_loop) @ee.on('event') async def event_handler(): cancel_me.cancel() @ee.on('error') def handle_error(exc): should_not_call.set_result(None) ee.emit('event') try: await wait_for(should_not_call, 0.1) except TimeoutError: pass else: raise PyeeTestError()
async def test_event(emitter: AsyncIOEventEmitter): async def stream_event(data: str): assert data == '1' emitter.on('stream', stream_event) emitter.emit('stream', '2')
class PuppetMock(Puppet): """mock for puppet""" def __init__(self, options: PuppetMockOptions, name: str = 'puppet-mock'): super().__init__(options, name) if not options.mocker: raise WechatyPuppetMockError('mocker in options is required') self.mocker: Mocker = options.mocker self.started: bool = False self.emitter = AsyncIOEventEmitter() async def message_image(self, message_id: str, image_type: ImageType) -> FileBox: """get image from message""" async def ding(self, data: Optional[str] = None): pass def on(self, event_name: str, caller): """listen event""" self.emitter.on(event_name, caller) def listener_count(self, event_name: str) -> int: """get the event count of the specific event""" listeners = self.emitter.listeners(event=event_name) return len(listeners) async def start(self) -> None: """star the account""" self.started = True if not self.mocker: raise WechatyPuppetMockError( 'PuppetMock should not start without mocker' ) def _emit_events(response: MockerResponse): """emit the events from the mocker""" payload_data = json.loads(response.payload) if response.type == int(EventType.EVENT_TYPE_MESSAGE): log.debug('receive message info <%s>', payload_data) event_message_payload = EventMessagePayload( message_id=payload_data['messageId']) self.emitter.emit('message', event_message_payload) self.mocker.on('stream', _emit_events) async def stop(self): """stop the account""" self.started = False async def contact_list(self) -> List[str]: """get all of the contact""" return self.mocker.get_contact_ids() async def tag_contact_delete(self, tag_id: str) -> None: pass async def tag_favorite_delete(self, tag_id: str) -> None: pass async def tag_contact_add(self, tag_id: str, contact_id: str): pass async def tag_favorite_add(self, tag_id: str, contact_id: str): pass async def tag_contact_remove(self, tag_id: str, contact_id: str): pass async def tag_contact_list(self, contact_id: Optional[str] = None) -> List[str]: pass async def message_send_text(self, conversation_id: str, message: str, mention_ids: List[str] = None) -> str: """send the text message to the specific contact/room""" conversation: Union[Room, Contact] if conversation_id.startswith('room-'): conversation = self.mocker.Room.load(conversation_id) else: conversation = self.mocker.Contact.load(conversation_id) message_id = self.mocker.send_message( talker=self.mocker.login_user, conversation=conversation, msg=message ) return message_id async def message_send_contact(self, contact_id: str, conversation_id: str) -> str: pass async def message_send_file(self, conversation_id: str, file: FileBox) -> str: pass async def message_send_url(self, conversation_id: str, url: str) -> str: pass async def message_send_mini_program(self, conversation_id: str, mini_program: MiniProgramPayload ) -> str: pass async def message_search(self, query: Optional[MessageQueryFilter] = None ) -> List[str]: pass async def message_recall(self, message_id: str) -> bool: pass async def message_payload(self, message_id: str) -> MessagePayload: """get the message payload""" return self.mocker.environment.get_message_payload( message_id=message_id) async def message_forward(self, to_id: str, message_id: str): pass async def message_file(self, message_id: str) -> FileBox: """get the file-box from message instance save the file-box data in message_payload.text field to avoid creating a new structure to support this feature """ message_payload = self.mocker.environment.get_message_payload( message_id=message_id ) return FileBox.from_json(message_payload.text) async def message_contact(self, message_id: str) -> str: """get the message Contact id info text field save the message contact_id info """ message_payload = self.mocker.environment.get_message_payload( message_id=message_id ) return message_payload.text async def message_url(self, message_id: str) -> UrlLinkPayload: """get the url link """ async def message_mini_program(self, message_id: str) -> MiniProgramPayload: pass async def contact_alias(self, contact_id: str, alias: Optional[str] = None) -> str: """get/save the contact alias""" contact_payload = self.mocker.environment.\ get_contact_payload(contact_id) if not alias: return contact_payload.alias contact_payload.alias = alias self.mocker.environment.update_contact_payload(contact_payload) return alias async def contact_payload_dirty(self, contact_id: str): pass async def contact_payload(self, contact_id: str) -> ContactPayload: """get the contact payload""" return self.mocker.environment.get_contact_payload(contact_id) async def contact_avatar(self, contact_id: str, file_box: Optional[FileBox] = None) -> FileBox: """get the contact avatar""" contact_payload = self.mocker.environment.\ get_contact_payload(contact_id) if not file_box: return FileBox.from_base64( contact_payload.avatar, name=f'{contact_payload.name}.png' ) contact_payload.avatar = file_box.base64 self.mocker.environment.update_contact_payload(contact_payload) async def contact_tag_ids(self, contact_id: str) -> List[str]: pass def self_id(self) -> str: return self.mocker.login_user.contact_id async def friendship_search(self, weixin: Optional[str] = None, phone: Optional[str] = None) -> Optional[str]: pass async def friendship_add(self, contact_id: str, hello: str): pass async def friendship_payload(self, friendship_id: str, payload: Optional[FriendshipPayload] = None ) -> FriendshipPayload: pass async def friendship_accept(self, friendship_id: str): pass async def room_list(self) -> List[str]: """get the room id list""" rooms = self.mocker.environment.get_room_payloads() return [room.id for room in rooms] async def room_create(self, contact_ids: List[str], topic: str = None) -> str: """create the room""" room_payload = self.mocker.environment.new_room_payload( member_ids=contact_ids, topic=topic ) return room_payload.id async def room_search(self, query: RoomQueryFilter = None) -> List[str]: pass async def room_invitation_payload(self, room_invitation_id: str, payload: Optional[ RoomInvitationPayload] = None ) -> RoomInvitationPayload: pass async def room_invitation_accept(self, room_invitation_id: str): pass async def contact_self_qr_code(self) -> str: pass async def contact_self_name(self, name: str): pass async def contact_signature(self, signature: str): pass async def room_payload(self, room_id: str) -> RoomPayload: """get the room payload""" return self.mocker.environment.get_room_payload(room_id) async def room_members(self, room_id: str) -> List[str]: """get the room member ids from environment Args: room_id (str): the union identification for room Returns: List[str]: room member ids """ room_payload: RoomPayload = self.mocker.environment.get_room_payload( room_id) return room_payload.member_ids async def room_add(self, room_id: str, contact_id: str): """add a contact to a room""" self.mocker.add_contact_to_room( contact_ids=[contact_id], room_id=room_id ) async def room_delete(self, room_id: str, contact_id: str): pass async def room_quit(self, room_id: str): pass async def room_topic(self, room_id: str, new_topic: str): pass async def room_announce(self, room_id: str, announcement: str = None) -> str: pass async def room_qr_code(self, room_id: str) -> str: pass async def room_member_payload(self, room_id: str, contact_id: str) -> RoomMemberPayload: pass async def room_avatar(self, room_id: str) -> FileBox: pass async def logout(self): pass async def login(self, user_id: str): """login the user data""" self.mocker.login(user_id=user_id)
async def _handleMessage(self, message: ServerMessage) -> None: """Handle messages from the server.""" type = message.type peerId = message.src payload = message.payload log.debug( '\n Handling server message \n type %s, ' '\n source peer/client id %s, \n message payload %s, ' '\n full message %r', type, peerId, payload, message) server_messenger = AsyncIOEventEmitter() # The connection to the server is open. @server_messenger.once(ServerMessageType.Open) def _on_server_open(): self._lastServerId = self.id self._open = True log.info('Signaling server connection open.') self.emit(PeerEventType.Open, self.id) # Server error. @server_messenger.once(ServerMessageType.Error) async def _on_server_error(): await self._abort(PeerErrorType.ServerError, payload.msg) # The selected ID is taken. @server_messenger.once(ServerMessageType.IdTaken) async def _on_server_idtaken(): await self._abort(PeerErrorType.UnavailableID, f'ID "${self.id}" is taken') # The given API key cannot be found. @server_messenger.once(ServerMessageType.InvalidKey) async def _on_server_invalidkey(): await self._abort(PeerErrorType.InvalidKey, f'API KEY "${self._options.key}" is invalid') # Another peer has closed its connection to this peer. @server_messenger.once(ServerMessageType.Leave) async def _on_server_leave(): log.debug(f'Received leave message from ${peerId}') await self._cleanupPeer(peerId) self._connections.delete(peerId) # The offer sent to a peer has expired without response. @server_messenger.once(ServerMessageType.Expire) def _on_server_expire(): self.emitError(PeerErrorType.PeerUnavailable, f'Could not connect to peer ${peerId}') # Server relaying offer for a direct connection from a remote peer @server_messenger.once(ServerMessageType.Offer) async def _on_server_offer(): await self._handle_offer(peerId, payload) # Something went wrong during emit message handling # @server_messenger.once('error') # def on_error(error_message): # log.error('Error on server message emit: %r', error_message) is_handled = server_messenger.emit(type) if not is_handled: if not payload: log.warn(f'You received a malformed message ' f'from ${peerId} of type ${type}') return connectionId = payload['connectionId'] connection = self.getConnection(peerId, connectionId) if connection and connection.peerConnection: # Pass it on. await connection.handleMessage(message) elif connectionId: # Store for possible later use self._storeMessage(connectionId, message) else: log.warn("You received an unrecognized message:", message)
class WebSocket: """The Binance DEX WebSocket Manager.""" def __init__( self, address: str = None, testnet: bool = False, keepalive: bool = True, loop: asyncio.AbstractEventLoop = None, url: str = None, ) -> None: if not url: self.url = TESTNET_URL if testnet else MAINNET_URL else: self.url = url self.address = address self._session = aiohttp.ClientSession() self._ws: Optional[aiohttp.ClientWebSocketResponse] = None self._loop = loop or asyncio.get_event_loop() self._events = AsyncIOEventEmitter(loop=self._loop) self._sub_queue: List[Tuple[str, dict]] = [] self._keepalive = keepalive self._keepalive_task: Optional[asyncio.Future] = None self._open = False self._testnet = testnet def on(self, event: str, func: Optional[Callable] = None, **kwargs): """Register an event, and optional handler. This can be used as a decorator or as a normal method. See `examples/websockets_decorator.py` for usage. """ # Queue up most events from startup-time decorators until after we are open if not self._open and event not in ("open", "error", "new_listener"): self._sub_queue.append((event, kwargs)) if func: self._events.on(event, func) return None else: return self._events.on(event) def start( self, on_open: Optional[Callable[[], None]] = None, on_error: Optional[Callable[[dict], None]] = None, loop: asyncio.AbstractEventLoop = None, ) -> None: """The main blocking call to start the WebSocket connection.""" loop = loop or asyncio.get_event_loop() return loop.run_until_complete(self.start_async(on_open, on_error)) async def start_async( self, on_open: Optional[Callable[[], None]] = None, on_error: Optional[Callable[[dict], None]] = None, ) -> None: """Processes all websocket messages.""" if self.address: # address-specific socket url = f"{self.url}/{self.address}" else: url = self.url async with self._session.ws_connect(url) as ws: self._ws = ws self._events.emit("open") while self._sub_queue: event, kwargs = self._sub_queue.pop() self.subscribe(event, **kwargs) if on_open: on_open() # Schedule keepalive calls every 30 minutes if self._keepalive: self._keepalive_task = asyncio.ensure_future( self._auto_keepalive()) async for msg in ws: if msg.type == aiohttp.WSMsgType.TEXT: try: data = msg.json(loads=orjson.loads) except Exception as e: log.error(f"Unable to decode msg: {msg}") continue if not data: log.error(f"Got empty msg: {msg}") continue if "error" in data: self._events.emit("error", data) if on_error: on_error(data) else: log.error(f"Unhandled error msg: {data}") continue if "stream" not in data: log.error(f"Got msg without stream: {data}") continue if "data" not in data: log.error(f"Got msg without data: {data}") continue self._events.emit(data["stream"], data) elif msg.type == aiohttp.WSMsgType.ERROR: log.error(msg) self._events.emit("error", msg) break async def send(self, data: dict) -> None: """Send data to the WebSocket""" if not self._ws: log.error("Error: Cannot send to uninitialized websocket") return await self._ws.send_bytes(orjson.dumps(data)) def subscribe( self, stream: str, symbols: Optional[List[str]] = None, address: Optional[str] = None, callback: Optional[Callable[[dict], None]] = None, ): """Subscribe to a WebSocket stream. See the documentation for more details on the available streams https://docs.binance.org/api-reference/dex-api/ws-streams.html """ payload: Dict[Any, Any] = {"method": "subscribe", "topic": stream} if symbols: payload["symbols"] = symbols if address: payload["address"] = address elif self.address: payload["address"] = self.address self._events.on(stream, callback) asyncio.ensure_future(self.send(payload)) def unsubscribe(self, stream, symbols=None) -> None: payload = {"method": "unsubscribe", "topic": stream} if symbols: payload["symbols"] = symbols asyncio.ensure_future(self.send(payload)) def subscribe_user_orders(self, callback: Callable[[dict], None], address: Optional[str] = None) -> None: """Subscribe to individual order updates.""" self.subscribe("orders", address=address, callback=callback) def subscribe_user_accounts(self, callback: Callable[[dict], None], address: Optional[str] = None) -> None: """Subscribe to account updates.""" self.subscribe("accounts", address=address, callback=callback) def subscribe_user_transfers(self, callback: Callable[[dict], None], address: Optional[str] = None) -> None: """ Subscribe to transfer updates if `address` is involved (as sender or receiver) in a transfer. Multisend is also covered. """ self.subscribe("transfers", address=address, callback=callback) def subscribe_trades(self, symbols: List[str], callback: Callable[[dict], None]) -> None: """Subscribe to individual trade updates.""" self.subscribe("trades", symbols=symbols, callback=callback) def subscribe_market_diff(self, symbols: List[str], callback: Callable[[dict], None]) -> None: "Order book price and quantity depth updates used to locally keep an order book." "" self.subscribe("marketDiff", symbols=symbols, callback=callback) def subscribe_market_depth(self, symbols: List[str], callback: Callable[[dict], None]) -> None: """Top 20 levels of bids and asks.""" self.subscribe("marketDepth", symbols=symbols, callback=callback) def subscribe_kline(self, interval: str, symbols: List[str], callback: Callable[[dict], None]) -> None: """ The kline/candlestick stream pushes updates to the current klines/candlestick every second. Kline/Candlestick chart intervals: m -> minutes; h -> hours; d -> days; w -> weeks; M -> months 1m 3m 5m 15m 30m 1h 2h 4h 6h 8h 12h 1d 3d 1w 1M """ self.subscribe(f"kline_{interval}", symbols=symbols, callback=callback) def subscribe_ticker(self, symbols: List[str], callback: Callable[[dict], None]) -> None: """24hr Ticker statistics for a single symbol are pushed every second.""" self.subscribe("ticker", symbols=symbols, callback=callback) def subscribe_all_tickers(self, callback: Callable[[dict], None]) -> None: """24hr Ticker statistics for a all symbols are pushed every second.""" self.subscribe("allTickers", symbols=["$all"], callback=callback) def subscribe_mini_ticker(self, symbols: List[str], callback: Callable[[dict], None]) -> None: """A ticker for a single symbol is pushed every second.""" self.subscribe("miniTicker", symbols=symbols, callback=callback) def subscribe_all_mini_tickers(self, callback: Callable[[dict], None]) -> None: """Array of 24hr Mini Ticker statistics for a all symbols pushed every second.""" self.subscribe("allMiniTickers", symbols=["$all"], callback=callback) def subscribe_blockheight(self, callback: Callable[[dict], None]) -> None: """Streams the latest block height.""" self.subscribe("blockheight", symbols=["$all"], callback=callback) def keepalive(self) -> None: """Extend the connection time by another 30 minutes""" asyncio.ensure_future(self.send({"method": "keepAlive"})) async def _auto_keepalive(self): while True: await asyncio.sleep(30 * 60) self.keepalive() def close(self) -> None: """Close the websocket session""" asyncio.ensure_future(self.send({"method": "close"})) if self._session: asyncio.ensure_future(self._session.close()) if self._keepalive_task: self._keepalive_task.cancel()