xml-pipeline/handlers/hello.py
dullfig 82b5fcdd78 Replace MessageBus with aiostream-based StreamPump
Major refactor of the message pump architecture:

- Replace bus.py with stream_pump.py using aiostream for composable
  stream processing with natural fan-out via flatmap
- Add to_id field to MessageState for explicit routing
- Fix routing to use to_id.class format (e.g., "greeter.greeting")
- Generate XSD schemas from xmlified payload classes
- Fix xmlable imports (absolute -> relative) and parse_element ctx

New features:
- handlers/hello.py: Sample Greeting/GreetingResponse handler
- config/organism.yaml: Sample organism configuration
- 41 tests (31 unit + 10 integration) all passing

Schema changes:
- envelope.xsd: Allow any namespace payloads (##other -> ##any)

Dependencies added to pyproject.toml:
- aiostream>=0.5 (core dependency)
- pyhumps, termcolor (for xmlable)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 10:41:17 -08:00

78 lines
2.1 KiB
Python

"""
hello.py — Hello World handler for testing the message pump.
This module provides:
- Greeting: payload class (what the handler receives)
- GreetingResponse: response payload (what the handler returns)
- handle_greeting: async handler function
Usage in organism.yaml:
listeners:
- name: greeter
payload_class: handlers.hello.Greeting
handler: handlers.hello.handle_greeting
description: Responds with a greeting message
"""
from dataclasses import dataclass
from lxml import etree
from third_party.xmlable import xmlify
from agentserver.message_bus.message_state import HandlerMetadata
# Envelope namespace
ENVELOPE_NS = "https://xml-pipeline.org/ns/envelope/v1"
@xmlify
@dataclass
class Greeting:
"""Incoming greeting request."""
name: str
@xmlify
@dataclass
class GreetingResponse:
"""Outgoing greeting response."""
message: str
def wrap_in_envelope(payload_bytes: bytes, from_id: str, to_id: str, thread_id: str) -> bytes:
"""Wrap a payload in a proper message envelope."""
return f"""<message xmlns="{ENVELOPE_NS}">
<meta>
<from>{from_id}</from>
<to>{to_id}</to>
<thread>{thread_id}</thread>
</meta>
{payload_bytes.decode('utf-8')}
</message>""".encode('utf-8')
async def handle_greeting(payload: Greeting, metadata: HandlerMetadata) -> bytes:
"""
Handle an incoming Greeting and respond with a GreetingResponse.
Args:
payload: The deserialized Greeting instance
metadata: Contains thread_id, from_id, own_name
Returns:
XML bytes of the response envelope
"""
# Create response
response = GreetingResponse(message=f"Hello, {payload.name}!")
# Serialize to XML
response_tree = response.xml_value("GreetingResponse")
payload_bytes = etree.tostring(response_tree, encoding='utf-8')
# Wrap in envelope - respond back to sender
return wrap_in_envelope(
payload_bytes=payload_bytes,
from_id=metadata.own_name or "greeter",
to_id=metadata.from_id, # Send back to whoever sent the greeting
thread_id=metadata.thread_id,
)