1515from __future__ import annotations
1616
1717import re
18- from typing import Any , Dict , Optional
18+ from typing import Any
1919
2020import aiohttp
2121
2727_W3C_SPAN_ID = re .compile (r"^[0-9a-f]{16}$" )
2828
2929
30+ def _build_event (route : str , body : Any , headers : dict [str , str ] | None ,
31+ from_route : str | None , cid : str | None ) -> EventEnvelope :
32+ """Build the outbound envelope, inheriting the current trace context."""
33+ event = EventEnvelope (to = route , body = body , headers = headers or {})
34+ if from_route :
35+ event .set_from (from_route )
36+ info = get_trace ()
37+ if info and info .trace_id :
38+ event .set_trace (info .trace_id , info .trace_path or route )
39+ effective_cid = cid or (info .cid if info else None )
40+ if effective_cid :
41+ event .set_correlation_id (effective_cid )
42+ return event
43+
44+
3045class PostOffice :
3146 """Event-over-HTTP client for calling functions on peer applications."""
3247
33- def __init__ (self , endpoint : Optional [ str ] = None ,
34- security_headers : Optional [ Dict [ str , str ]] = None ):
48+ def __init__ (self , endpoint : str | None = None ,
49+ security_headers : dict [ str , str ] | None = None ):
3550 self .endpoint = endpoint
3651 self .security_headers = dict (security_headers or {})
37- self ._session : Optional [ aiohttp .ClientSession ] = None
52+ self ._session : aiohttp .ClientSession | None = None
3853
39- async def _get_session (self ) -> aiohttp .ClientSession :
40- if self ._session is None or self ._session .closed :
41- self ._session = aiohttp .ClientSession ()
42- return self ._session
54+ def _get_session (self ) -> aiohttp .ClientSession :
55+ # called from the running event loop only (inside request/send)
56+ session = self ._session
57+ if session is None or session .closed :
58+ session = aiohttp .ClientSession ()
59+ self ._session = session
60+ return session
4361
4462 async def close (self ) -> None :
4563 if self ._session is not None and not self ._session .closed :
4664 await self ._session .close ()
4765
48- async def __aenter__ (self ) -> "PostOffice" :
66+ # PYI034 wants '-> Self', which needs python >= 3.11; switch when the
67+ # floor moves past 3.10
68+ async def __aenter__ (self ) -> PostOffice : # noqa: PYI034
4969 return self
5070
51- async def __aexit__ (self , * _exc ) -> None :
71+ async def __aexit__ (self , * _exc : object ) -> None :
5272 await self .close ()
5373
5474 def _http_headers (self , timeout_ms : int , is_async : bool ,
55- event : EventEnvelope ) -> Dict [str , str ]:
75+ event : EventEnvelope ) -> dict [str , str ]:
5676 headers = {
5777 "content-type" : "application/octet-stream" ,
5878 "accept" : "*/*" ,
@@ -71,28 +91,15 @@ def _http_headers(self, timeout_ms: int, is_async: bool,
7191 headers ["traceparent" ] = f"00-{ event .trace_id } -{ event .span_id } -01"
7292 return headers
7393
74- def _build_event (self , route : str , body : Any , headers : Optional [Dict [str , str ]],
75- from_route : Optional [str ], cid : Optional [str ]) -> EventEnvelope :
76- event = EventEnvelope (to = route , body = body , headers = headers or {})
77- if from_route :
78- event .set_from (from_route )
79- info = get_trace ()
80- if info and info .trace_id :
81- event .set_trace (info .trace_id , info .trace_path or route )
82- effective_cid = cid or (info .cid if info else None )
83- if effective_cid :
84- event .set_correlation_id (effective_cid )
85- return event
86-
87- async def _call (self , route : str , body : Any , headers : Optional [Dict [str , str ]],
88- timeout_ms : int , endpoint : Optional [str ], is_async : bool ,
89- from_route : Optional [str ], cid : Optional [str ]) -> EventEnvelope :
94+ async def _call (self , route : str , body : Any , headers : dict [str , str ] | None ,
95+ timeout_ms : int , endpoint : str | None , is_async : bool ,
96+ from_route : str | None , cid : str | None ) -> EventEnvelope :
9097 url = endpoint or self .endpoint
9198 if not url :
9299 raise ValueError ("Missing event endpoint - "
93100 "e.g. PostOffice(endpoint='http://peer:8085/api/event')" )
94- event = self . _build_event (route , body , headers , from_route , cid )
95- session = await self ._get_session ()
101+ event = _build_event (route , body , headers , from_route , cid )
102+ session = self ._get_session ()
96103 # +100 ms cushion so the HTTP client does not time out before the target
97104 client_timeout = aiohttp .ClientTimeout (total = (max (100 , timeout_ms ) + 100 ) / 1000 )
98105 async with session .post (url , data = event .to_bytes (),
@@ -106,21 +113,21 @@ async def _call(self, route: str, body: Any, headers: Optional[Dict[str, str]],
106113 f"Invalid event-over-http response - { e } " ) from e
107114
108115 async def request (self , route : str , body : Any = None , * ,
109- headers : Optional [ Dict [ str , str ]] = None ,
116+ headers : dict [ str , str ] | None = None ,
110117 timeout_ms : int = 30000 ,
111- endpoint : Optional [ str ] = None ,
112- from_route : Optional [ str ] = None ,
113- cid : Optional [ str ] = None ) -> EventEnvelope :
118+ endpoint : str | None = None ,
119+ from_route : str | None = None ,
120+ cid : str | None = None ) -> EventEnvelope :
114121 """RPC call: returns the target function's reply envelope."""
115122 return await self ._call (route , body , headers , timeout_ms , endpoint ,
116123 False , from_route , cid )
117124
118125 async def send (self , route : str , body : Any = None , * ,
119- headers : Optional [ Dict [ str , str ]] = None ,
126+ headers : dict [ str , str ] | None = None ,
120127 timeout_ms : int = 30000 ,
121- endpoint : Optional [ str ] = None ,
122- from_route : Optional [ str ] = None ,
123- cid : Optional [ str ] = None ) -> EventEnvelope :
128+ endpoint : str | None = None ,
129+ from_route : str | None = None ,
130+ cid : str | None = None ) -> EventEnvelope :
124131 """Drop-n-forget: returns the peer's 202 delivery acknowledgement envelope."""
125132 return await self ._call (route , body , headers , timeout_ms , endpoint ,
126133 True , from_route , cid )
0 commit comments