Repository navigation
feat: Request[Result] typing for send and stream
Summary
Request[Result] carries a result. Request[None] is an empty result. RequestHandler[ConcreteRequest] takes that single argument. handle has no return annotation: the overloads are what checkers use, and the implementation omits the annotation so PyCharm infers the concrete result. RequestMediator.send(request: IRequest[_SendResT]) -> _SendResT. A one-argument subscription still stores (request, response) at runtime.
class ReadMeetingQueryResult(cqrs.Response):
users: list[str]
class ReadMeetingQuery(cqrs.Request[ReadMeetingQueryResult]):
meeting_id: str
class JoinMeetingCommand(cqrs.Request[None]):
user_id: str
meeting_id: str
class ReadMeetingQueryHandler(cqrs.RequestHandler[ReadMeetingQuery]):
async def handle(self, request: ReadMeetingQuery):
return ReadMeetingQueryResult(users=list(STORAGE[request.meeting_id]))
class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand]):
async def handle(self, request: JoinMeetingCommand):
STORAGE[request.meeting_id].append(request.user_id)
assert await mediator.send(JoinMeetingCommand(user_id="1", meeting_id="1")) is None
assert await mediator.send(JoinMeetingCommand(user_id="2", meeting_id="1")) is None
users = await mediator.send(ReadMeetingQuery(meeting_id="1"))
assert users.users == ["1", "2"]That sample is examples/events/typed_request_handler.py. users is ReadMeetingQueryResult. Each JoinMeetingCommand send is None.
An unparameterized Request stays Request[Any], so send on it stays Any. RequestHandler[Request, Result] stays valid, and that form may still annotate handle. Existing tests and examples keep working.
class BareQuery(cqrs.Request):
meeting_id: str
bare = await mediator.send(BareQuery(meeting_id="1"))
class OldReadMeetingQueryHandler(cqrs.RequestHandler[ReadMeetingQuery, ReadMeetingQueryResult]):
async def handle(self, request: ReadMeetingQuery) -> ReadMeetingQueryResult:
return ReadMeetingQueryResult(users=["old"])bare is Any. The two-argument handler is the form already in the tree.
src/cqrs/py.typedmarks the package typed (PEP 561) so checkers use the annotations shipped in the wheel.typing-extensionsis>=4.7, which is whatTypeVardefaults need. Version 5.1.0 to 5.2.0.AGENTS.mdis for contributors changing this library: public exports, the Python 3.10 floor, and this request shape.CLAUDE.mdpoints at it.templates/AGENTS.mdis the file an application author copies. It describes calling the package.examples/events/typed_request_handler.pyruns the query and command above.examples/streaming/typed_streaming_handler.pyis the streaming form:ProcessItemsCommandisRequest[ItemResult], andStreamingRequestHandler[ProcessItemsCommand].handleyieldsItemResultwith no element annotation.examples/cor/typed_cor_request_handler.pyis the chain form:CreditCardHandlerisCORRequestHandler[ProcessPaymentCommand]andhandlehas no return annotation. Older examples are unchanged.
README samples use the one-argument handler. The three Transactional Outbox tips render as separate GitHub alerts. The README no longer calls the project a Diator fork. CONTRIBUTING still says "Fork and Clone".