simplex-chat-python: split Client from Bot, add request/response API (#6976)

* simplex-chat-python: split Client from Bot, add request/response API

Client is now the base class for SimpleX participants that talk TO
services (monitors, probes, automated participants). Bot extends Client
with server features (address, auto-accept, welcome, commands).

New methods on Client (inherited by Bot):
  connect_to(link)           idempotent contact handshake
  send_and_wait(id, text)    send a message and await the reply
  events()                   async iterator over chat events
  @on_message(contact_id=N)  filter by sender in decorators

BotProfile renamed to Profile (alias kept). New ContactAlreadyExistsError
subclass for cleaner error handling.

* simplex-chat-python: narrow event payload type per @on_event tag

@client.on_event("contactConnected") now types the handler's event
parameter as CEvt.ContactConnected instead of the unnarrowed
CEvt.ChatEvent union — mirroring how @on_message narrows by
content_type.

The 50 overloads are generated by the Haskell codegen into _events.py
(as a Protocol class), so new events stay in sync automatically.
Client.on_event is exposed as a property typed as that Protocol; the
runtime implementation is unchanged.
This commit is contained in:
sh
2026-05-13 15:51:00 +00:00
committed by GitHub
parent 3b4bf92015
commit 9584992c83
10 changed files with 2089 additions and 663 deletions
+37 -1
View File
@@ -83,12 +83,48 @@ responsesCodeText =
eventsCodeText :: Text
eventsCodeText =
("# API Events\n# " <> autoGenerated <> "\n")
<> pythonImports
<> "from __future__ import annotations\n"
<> "from collections.abc import Awaitable, Callable\n"
<> "from typing import Literal, NotRequired, Protocol, TypedDict, overload\n"
<> "from . import _types as T\n"
<> unionTypeCodePy moduleMember "T." "ChatEvent" chatEventConstrs
<> onEventProtocolCode chatEventConstrs
where
chatEventConstrs = L.fromList $ concatMap catEvents chatEventsDocs
catEvents CECategory {mainEvents, otherEvents} = map eventType $ mainEvents ++ otherEvents
-- | Render the `OnEventDecorator` Protocol — one `__call__` overload per
-- event tag, narrowing the handler's event parameter from the unnarrowed
-- `ChatEvent` union to the specific tagged TypedDict. Plus a fallback
-- overload for `event: str` that keeps the unnarrowed shape so non-literal
-- tags don't trigger a type error.
--
-- `Client.on_event` is typed as a `OnEventDecorator` (via a property) so
-- callers get per-tag narrowing without per-tag handwritten overloads
-- in client.py.
onEventProtocolCode :: L.NonEmpty ATUnionMember -> Text
onEventProtocolCode members =
"\n\nclass OnEventDecorator(Protocol):\n"
<> " \"\"\"Per-tag narrowing protocol for ``Client.on_event``.\n"
<> "\n"
<> " ``@client.on_event(\"contactConnected\")`` types the handler's\n"
<> " ``evt`` parameter as :class:`ContactConnected` rather than the\n"
<> " unnarrowed :data:`ChatEvent` union.\n"
<> " \"\"\"\n"
<> foldMap overloadCode (L.toList members)
<> "\n @overload\n"
<> " def __call__(self, event: str, /) -> Callable[\n"
<> " [Callable[[\"ChatEvent\"], Awaitable[None]]],\n"
<> " Callable[[\"ChatEvent\"], Awaitable[None]],\n"
<> " ]: ...\n"
where
overloadCode (ATUnionMember tag _) =
"\n @overload\n"
<> " def __call__(self, event: Literal[\"" <> T.pack tag <> "\"], /) -> Callable[\n"
<> " [Callable[[\"" <> pyConstrName tag <> "\"], Awaitable[None]]],\n"
<> " Callable[[\"" <> pyConstrName tag <> "\"], Awaitable[None]],\n"
<> " ]: ...\n"
typesCodeText :: Text
typesCodeText =
("# API Types\n# " <> autoGenerated <> "\n")