@@ -122,10 +122,14 @@ def attempt(self) -> int:
122122 """The attempt this producer writes."""
123123 return self ._attempt
124124
125- async def append (self , * values : T ) -> Cursor :
125+ async def append (self , * values : T | StreamLink ) -> Cursor :
126126 """Append ``values`` as one batch and return the cursor of its last record.
127127
128- The batch lands whole and in order. The producer moves its sequence
128+ A :class:`temporalio.contrib.streams.StreamLink` among the values is a
129+ ``LINK`` record, as :meth:`link` and :meth:`join` write it, and the
130+ other values are ``DATA`` records. So one batch can hold a value and
131+ the link that goes with it, under one dedupe identity. The batch lands
132+ whole and in order. The producer moves its sequence
129133 past the batch only when the append succeeds, so a call that raised
130134 :class:`temporalio.contrib.streams.StreamOutcomeUnknownError` can be
131135 repeated with the same values: the store answers a repeat of the
@@ -151,11 +155,19 @@ async def append(self, *values: T) -> Cursor:
151155 StreamRefusedError: The store refused the batch, for example
152156 because it is out of memory. Nothing was written.
153157 StreamClosedError: The stream refuses appends.
158+ ValueError: A link names nothing, or names a stream that no
159+ Workflow owns. Nothing was written.
154160 """
161+ items : list [tuple [RecordKind , Any , StreamLink | None ]] = [
162+ (RecordKind .LINK , None , v )
163+ if isinstance (v , StreamLink )
164+ else (RecordKind .DATA , v , None )
165+ for v in values
166+ ]
155167 async with self ._lock :
156- if not values :
168+ if not items :
157169 return self ._last
158- return await self ._write (RecordKind . DATA , values )
170+ return await self ._write (items )
159171
160172 async def finish (self ) -> Cursor :
161173 """Write ``FINISH`` for this producer on this topic and return its cursor.
@@ -164,7 +176,7 @@ async def finish(self) -> Cursor:
164176 work behind it succeeded, and it does not end anyone's read.
165177 """
166178 async with self ._lock :
167- return await self ._write (RecordKind .FINISH , [ None ])
179+ return await self ._write ([( RecordKind .FINISH , None , None ) ])
168180
169181 async def link (
170182 self ,
@@ -189,7 +201,7 @@ async def link(
189201 """
190202 link = StreamLink (LinkEvent .OPEN , ref , name , after = after )
191203 async with self ._lock :
192- return await self ._write (RecordKind .LINK , [ value ] , link )
204+ return await self ._write ([( RecordKind .LINK , value , link )] )
193205
194206 async def join (self , * , name : str , until : Cursor | None = None ) -> Cursor :
195207 """Write a ``LINK`` that says this producer waited for link ``name``.
@@ -203,10 +215,10 @@ async def join(self, *, name: str, until: Cursor | None = None) -> Cursor:
203215 """
204216 link = StreamLink (LinkEvent .JOIN , None , name , until = until )
205217 async with self ._lock :
206- return await self ._write (RecordKind .LINK , [ None ] , link )
218+ return await self ._write ([( RecordKind .LINK , None , link )] )
207219
208220 async def _write (
209- self , kind : RecordKind , values : Any , link : StreamLink | None = None
221+ self , items : list [ tuple [ RecordKind , Any , StreamLink | None ]]
210222 ) -> Cursor :
211223 converter = self ._handle ._converter
212224 wires = [
@@ -220,7 +232,7 @@ async def _write(
220232 sequence = self ._sequence + index ,
221233 link = link ,
222234 )
223- for index , value in enumerate (values )
235+ for index , ( kind , value , link ) in enumerate (items )
224236 ]
225237 request = AppendRequest (
226238 stream = self ._handle ._address (self ._topic ),
0 commit comments