1use indexmap::IndexMap;
17use nautilus_core::{
18 UUID4, UnixNanos,
19 python::{
20 IntoPyObjectNautilusExt,
21 parsing::{get_optional, get_optional_parsed, get_required, get_required_parsed},
22 to_pyruntime_err, to_pyvalue_err,
23 },
24};
25use pyo3::{basic::CompareOp, prelude::*, types::PyDict};
26use rust_decimal::Decimal;
27use ustr::Ustr;
28
29use crate::{
30 enums::{
31 ContingencyType, OrderSide, OrderStatus, OrderType, PositionSide, TimeInForce,
32 TrailingOffsetType, TriggerType,
33 },
34 events::order::initialized::OrderInitialized,
35 identifiers::{
36 ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, StrategyId, TraderId,
37 },
38 orders::{Order, OrderCore, TrailingStopLimitOrder, str_indexmap_to_ustr},
39 python::{
40 common::commissions_from_indexmap,
41 events::order::{order_event_to_pyobject, pyobject_to_order_event},
42 },
43 types::{Currency, Money, Price, Quantity},
44};
45
46#[pymethods]
47#[pyo3_stub_gen::derive::gen_stub_pymethods]
48impl TrailingStopLimitOrder {
49 #[new]
51 #[expect(clippy::too_many_arguments)]
52 #[pyo3(signature = (trader_id, strategy_id, instrument_id, client_order_id, order_side, quantity, price, trigger_price, trigger_type, limit_offset, trailing_offset, trailing_offset_type, time_in_force, post_only, reduce_only, quote_quantity, init_id, ts_init, activation_price=None, expire_time=None, display_qty=None, emulation_trigger=None, trigger_instrument_id=None, contingency_type=None, order_list_id=None, linked_order_ids=None, parent_order_id=None, exec_algorithm_id=None, exec_algorithm_params=None, exec_spawn_id=None, tags=None))]
53 fn py_new(
54 trader_id: TraderId,
55 strategy_id: StrategyId,
56 instrument_id: InstrumentId,
57 client_order_id: ClientOrderId,
58 order_side: OrderSide,
59 quantity: Quantity,
60 price: Option<Price>,
61 trigger_price: Option<Price>,
62 trigger_type: TriggerType,
63 limit_offset: Decimal,
64 trailing_offset: Decimal,
65 trailing_offset_type: TrailingOffsetType,
66 time_in_force: TimeInForce,
67 post_only: bool,
68 reduce_only: bool,
69 quote_quantity: bool,
70 init_id: UUID4,
71 ts_init: u64,
72 activation_price: Option<Price>,
73 expire_time: Option<u64>,
74 display_qty: Option<Quantity>,
75 emulation_trigger: Option<TriggerType>,
76 trigger_instrument_id: Option<InstrumentId>,
77 contingency_type: Option<ContingencyType>,
78 order_list_id: Option<OrderListId>,
79 linked_order_ids: Option<Vec<ClientOrderId>>,
80 parent_order_id: Option<ClientOrderId>,
81 exec_algorithm_id: Option<ExecAlgorithmId>,
82 exec_algorithm_params: Option<IndexMap<String, String>>,
83 exec_spawn_id: Option<ClientOrderId>,
84 tags: Option<Vec<String>>,
85 ) -> PyResult<Self> {
86 Self::new_checked(
87 trader_id,
88 strategy_id,
89 instrument_id,
90 client_order_id,
91 order_side,
92 quantity,
93 activation_price,
94 price,
95 trigger_price,
96 trigger_type,
97 limit_offset,
98 trailing_offset,
99 trailing_offset_type,
100 time_in_force,
101 expire_time.map(std::convert::Into::into),
102 post_only,
103 reduce_only,
104 quote_quantity,
105 display_qty,
106 emulation_trigger,
107 trigger_instrument_id,
108 contingency_type,
109 order_list_id,
110 linked_order_ids,
111 parent_order_id,
112 exec_algorithm_id,
113 exec_algorithm_params.map(str_indexmap_to_ustr),
114 exec_spawn_id,
115 tags.map(|vec| vec.into_iter().map(|s| Ustr::from(s.as_str())).collect()),
116 init_id,
117 ts_init.into(),
118 )
119 .map_err(to_pyvalue_err)
120 }
121
122 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
123 match op {
124 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
125 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
126 _ => py.NotImplemented(),
127 }
128 }
129
130 fn __repr__(&self) -> String {
131 self.to_string()
132 }
133
134 fn __str__(&self) -> String {
135 self.to_string()
136 }
137
138 #[staticmethod]
139 #[pyo3(name = "create")]
140 fn py_create(init: OrderInitialized) -> PyResult<Self> {
141 Self::try_from(init).map_err(to_pyvalue_err)
142 }
143
144 #[staticmethod]
145 #[pyo3(name = "opposite_side")]
146 fn py_opposite_side(side: OrderSide) -> OrderSide {
147 OrderCore::opposite_side(side)
148 }
149
150 #[staticmethod]
151 #[pyo3(name = "closing_side")]
152 fn py_closing_side(side: PositionSide) -> Option<OrderSide> {
153 OrderCore::closing_side(side)
154 }
155
156 #[getter]
157 #[pyo3(name = "status")]
158 fn py_status(&self) -> OrderStatus {
159 self.status
160 }
161
162 #[getter]
163 #[pyo3(name = "trader_id")]
164 fn py_trader_id(&self) -> TraderId {
165 self.trader_id
166 }
167
168 #[getter]
169 #[pyo3(name = "strategy_id")]
170 fn py_strategy_id(&self) -> StrategyId {
171 self.strategy_id
172 }
173
174 #[getter]
175 #[pyo3(name = "instrument_id")]
176 fn py_instrument_id(&self) -> InstrumentId {
177 self.instrument_id
178 }
179
180 #[getter]
181 #[pyo3(name = "client_order_id")]
182 fn py_client_order_id(&self) -> ClientOrderId {
183 self.client_order_id
184 }
185
186 #[getter]
187 #[pyo3(name = "side")]
188 fn py_order_side(&self) -> OrderSide {
189 self.side
190 }
191
192 #[getter]
193 #[pyo3(name = "quantity")]
194 fn py_quantity(&self) -> Quantity {
195 self.quantity
196 }
197
198 #[getter]
199 #[pyo3(name = "price")]
200 fn py_price(&self) -> Option<Price> {
201 self.price
202 }
203
204 #[getter]
205 #[pyo3(name = "trigger_price")]
206 fn py_trigger_price(&self) -> Option<Price> {
207 self.trigger_price
208 }
209
210 #[getter]
211 #[pyo3(name = "trigger_type")]
212 fn py_trigger_type(&self) -> TriggerType {
213 self.trigger_type
214 }
215
216 #[getter]
217 #[pyo3(name = "limit_offset")]
218 fn py_limit_offset(&self) -> Decimal {
219 self.limit_offset
220 }
221
222 #[getter]
223 #[pyo3(name = "trailing_offset")]
224 fn py_trailing_offset(&self) -> Decimal {
225 self.trailing_offset
226 }
227
228 #[getter]
229 #[pyo3(name = "trailing_offset_type")]
230 fn py_trailing_offset_type(&self) -> TrailingOffsetType {
231 self.trailing_offset_type
232 }
233
234 #[getter]
235 #[pyo3(name = "activation_price")]
236 fn py_activation_price(&self) -> Option<Price> {
237 self.activation_price
238 }
239
240 #[getter]
241 #[pyo3(name = "order_type")]
242 fn py_order_type(&self) -> OrderType {
243 self.order_type
244 }
245
246 #[getter]
247 #[pyo3(name = "time_in_force")]
248 fn py_time_in_force(&self) -> TimeInForce {
249 self.time_in_force
250 }
251
252 #[getter]
253 #[pyo3(name = "expire_time")]
254 fn py_expire_time(&self) -> Option<u64> {
255 self.expire_time.map(std::convert::Into::into)
256 }
257
258 #[getter]
259 #[pyo3(name = "init_id")]
260 fn py_init_id(&self) -> UUID4 {
261 self.init_id
262 }
263
264 #[getter]
265 #[pyo3(name = "ts_init")]
266 fn py_ts_init(&self) -> u64 {
267 self.ts_init.as_u64()
268 }
269
270 #[getter]
271 #[pyo3(name = "init_event")]
272 fn py_init_event(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
273 match self.init_event() {
274 Some(event) => order_event_to_pyobject(py, event),
275 None => Ok(py.None()),
276 }
277 }
278
279 #[getter]
280 #[pyo3(name = "has_price")]
281 fn py_has_price(&self) -> bool {
282 self.price.is_some()
283 }
284
285 #[getter]
286 #[pyo3(name = "has_trigger_price")]
287 fn py_has_trigger_price(&self) -> bool {
288 true
289 }
290
291 #[getter]
292 #[pyo3(name = "is_passive")]
293 fn py_is_passive(&self) -> bool {
294 self.is_passive()
295 }
296
297 #[getter]
298 #[pyo3(name = "is_aggressive")]
299 fn py_is_aggressive(&self) -> bool {
300 self.is_aggressive()
301 }
302
303 #[getter]
304 #[pyo3(name = "is_closed")]
305 fn py_is_closed(&self) -> bool {
306 self.is_closed()
307 }
308
309 #[getter]
310 #[pyo3(name = "is_open")]
311 fn py_is_open(&self) -> bool {
312 self.is_open()
313 }
314
315 #[getter]
316 #[pyo3(name = "is_post_only")]
317 fn py_post_only(&self) -> bool {
318 self.is_post_only
319 }
320
321 #[getter]
322 #[pyo3(name = "is_reduce_only")]
323 fn py_reduce_only(&self) -> bool {
324 self.is_reduce_only
325 }
326
327 #[getter]
328 #[pyo3(name = "is_quote_quantity")]
329 fn py_quote_quantity(&self) -> bool {
330 self.is_quote_quantity
331 }
332
333 #[getter]
334 #[pyo3(name = "display_qty")]
335 fn py_display_qty(&self) -> Option<Quantity> {
336 self.display_qty
337 }
338
339 #[getter]
340 #[pyo3(name = "emulation_trigger")]
341 fn py_emulation_trigger(&self) -> Option<TriggerType> {
342 self.emulation_trigger
343 }
344
345 #[getter]
346 #[pyo3(name = "trigger_instrument_id")]
347 fn py_trigger_instrument_id(&self) -> Option<InstrumentId> {
348 self.trigger_instrument_id
349 }
350
351 #[getter]
352 #[pyo3(name = "contingency_type")]
353 fn py_contingency_type(&self) -> Option<ContingencyType> {
354 self.contingency_type
355 }
356
357 #[getter]
358 #[pyo3(name = "order_list_id")]
359 fn py_order_list_id(&self) -> Option<OrderListId> {
360 self.order_list_id
361 }
362
363 #[getter]
364 #[pyo3(name = "linked_order_ids")]
365 fn py_linked_order_ids(&self) -> Option<Vec<ClientOrderId>> {
366 self.linked_order_ids.clone()
367 }
368
369 #[getter]
370 #[pyo3(name = "parent_order_id")]
371 fn py_parent_order_id(&self) -> Option<ClientOrderId> {
372 self.parent_order_id
373 }
374
375 #[getter]
376 #[pyo3(name = "exec_algorithm_id")]
377 fn py_exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
378 self.exec_algorithm_id
379 }
380
381 #[getter]
382 #[pyo3(name = "exec_algorithm_params")]
383 fn py_exec_algorithm_params(&self) -> Option<IndexMap<&str, &str>> {
384 self.exec_algorithm_params
385 .as_ref()
386 .map(|x| x.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect())
387 }
388
389 #[getter]
390 #[pyo3(name = "exec_spawn_id")]
391 fn py_exec_spawn_id(&self) -> Option<ClientOrderId> {
392 self.exec_spawn_id
393 }
394
395 #[getter]
396 #[pyo3(name = "account_id")]
397 fn py_account_id(&self) -> Option<crate::identifiers::AccountId> {
398 self.account_id
399 }
400
401 #[getter]
402 #[pyo3(name = "tags")]
403 fn py_tags(&self) -> Option<Vec<&str>> {
404 self.tags
405 .as_ref()
406 .map(|vec| vec.iter().map(Ustr::as_str).collect())
407 }
408
409 #[pyo3(name = "commission")]
410 fn py_commission(&self, currency: &Currency) -> Option<Money> {
411 self.commission(currency)
412 }
413
414 #[pyo3(name = "commissions")]
415 fn py_commissions(&self) -> IndexMap<Currency, Money> {
416 self.commissions().clone()
417 }
418
419 #[pyo3(name = "events")]
420 fn py_events(&self, py: Python<'_>) -> PyResult<Vec<Py<PyAny>>> {
421 self.events()
422 .into_iter()
423 .map(|event| order_event_to_pyobject(py, event.clone()))
424 .collect()
425 }
426
427 #[pyo3(name = "signed_decimal_qty")]
428 fn py_signed_decimal_qty(&self) -> Decimal {
429 self.signed_decimal_qty()
430 }
431
432 #[pyo3(name = "would_reduce_only")]
433 fn py_would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
434 self.would_reduce_only(side, position_qty)
435 }
436
437 #[pyo3(name = "apply")]
438 fn py_apply(&mut self, event: Py<PyAny>, py: Python<'_>) -> PyResult<()> {
439 let event_any = pyobject_to_order_event(py, event)?;
440 self.apply(event_any).map_err(to_pyruntime_err)
441 }
442
443 #[staticmethod]
444 #[pyo3(name = "from_dict")]
445 fn py_from_dict(values: &Bound<'_, PyDict>) -> PyResult<Self> {
446 let trader_id = get_required_parsed(values, "trader_id", |s| {
447 TraderId::new_checked(s).map_err(|e| e.to_string())
448 })?;
449
450 let strategy_id = get_required_parsed(values, "strategy_id", |s| {
451 StrategyId::new_checked(s).map_err(|e| e.to_string())
452 })?;
453
454 let instrument_id = get_required_parsed(values, "instrument_id", |s| {
455 s.parse::<InstrumentId>().map_err(|e| e.to_string())
456 })?;
457
458 let client_order_id = get_required_parsed(values, "client_order_id", |s| {
459 ClientOrderId::new_checked(s).map_err(|e| e.to_string())
460 })?;
461
462 let order_side = get_required_parsed(values, "side", |s| {
463 s.parse::<OrderSide>().map_err(|e| e.to_string())
464 })?;
465
466 let quantity = get_required_parsed(values, "quantity", |s| s.parse::<Quantity>())?;
467 let activation_price =
468 get_optional_parsed(values, "activation_price", |s| s.parse::<Price>())?;
469 let price = get_optional_parsed(values, "price", |s| s.parse::<Price>())?;
470 let trigger_price = get_optional_parsed(values, "trigger_price", |s| s.parse::<Price>())?;
471
472 let trigger_type = get_required_parsed(values, "trigger_type", |s| {
473 s.parse::<TriggerType>().map_err(|e| e.to_string())
474 })?;
475 let limit_offset = get_required_parsed(values, "limit_offset", |s| {
476 s.parse::<Decimal>().map_err(|e| e.to_string())
477 })?;
478 let trailing_offset = get_required_parsed(values, "trailing_offset", |s| {
479 s.parse::<Decimal>().map_err(|e| e.to_string())
480 })?;
481 let trailing_offset_type = get_required_parsed(values, "trailing_offset_type", |s| {
482 s.parse::<TrailingOffsetType>().map_err(|e| e.to_string())
483 })?;
484 let time_in_force = get_required_parsed(values, "time_in_force", |s| {
485 s.parse::<TimeInForce>().map_err(|e| e.to_string())
486 })?;
487 let post_only = get_required::<bool>(values, "is_post_only")?;
488 let reduce_only = get_required::<bool>(values, "is_reduce_only")?;
489 let quote_quantity = get_required::<bool>(values, "is_quote_quantity")?;
490 let expire_time = get_optional::<u64>(values, "expire_time_ns")?.map(UnixNanos::from);
491 let display_quantity =
492 get_optional_parsed(values, "display_qty", |s| s.parse::<Quantity>())?;
493
494 let emulation_trigger = get_optional_parsed(values, "emulation_trigger", |s| {
495 s.parse::<TriggerType>().map_err(|e| e.to_string())
496 })?;
497 let trigger_instrument_id = get_optional_parsed(values, "trigger_instrument_id", |s| {
498 s.parse::<InstrumentId>().map_err(|e| e.to_string())
499 })?;
500 let contingency_type = get_optional_parsed(values, "contingency_type", |s| {
501 s.parse::<ContingencyType>().map_err(|e| e.to_string())
502 })?;
503
504 let order_list_id = get_optional_parsed(values, "order_list_id", |s| {
505 OrderListId::new_checked(s).map_err(|e| e.to_string())
506 })?;
507
508 let linked_order_ids = get_optional::<Vec<String>>(values, "linked_order_ids")?
509 .map(|vec| {
510 vec.into_iter()
511 .map(ClientOrderId::new_checked)
512 .collect::<Result<Vec<_>, _>>()
513 })
514 .transpose()
515 .map_err(|e| to_pyvalue_err(format!("Failed to parse 'linked_order_ids': {e}")))?;
516
517 let parent_order_id = get_optional_parsed(values, "parent_order_id", |s| {
518 ClientOrderId::new_checked(s).map_err(|e| e.to_string())
519 })?;
520
521 let exec_algorithm_id = get_optional_parsed(values, "exec_algorithm_id", |s| {
522 ExecAlgorithmId::new_checked(s).map_err(|e| e.to_string())
523 })?;
524 let exec_algorithm_params =
525 get_optional::<IndexMap<String, String>>(values, "exec_algorithm_params")?
526 .map(str_indexmap_to_ustr);
527
528 let exec_spawn_id = get_optional_parsed(values, "exec_spawn_id", |s| {
529 ClientOrderId::new_checked(s).map_err(|e| e.to_string())
530 })?;
531 let tags = get_optional::<Vec<String>>(values, "tags")?
532 .map(|vec| vec.iter().map(|s| Ustr::from(s)).collect());
533 let init_id = get_required_parsed(values, "init_id", |s| s.parse::<UUID4>())?;
534 let ts_init = get_required::<u64>(values, "ts_init")?;
535 let order = Self::new_checked(
536 trader_id,
537 strategy_id,
538 instrument_id,
539 client_order_id,
540 order_side,
541 quantity,
542 activation_price,
543 price,
544 trigger_price,
545 trigger_type,
546 limit_offset,
547 trailing_offset,
548 trailing_offset_type,
549 time_in_force,
550 expire_time,
551 post_only,
552 reduce_only,
553 quote_quantity,
554 display_quantity,
555 emulation_trigger,
556 trigger_instrument_id,
557 contingency_type,
558 order_list_id,
559 linked_order_ids,
560 parent_order_id,
561 exec_algorithm_id,
562 exec_algorithm_params,
563 exec_spawn_id,
564 tags,
565 init_id,
566 ts_init.into(),
567 )
568 .map_err(to_pyvalue_err)?;
569 Ok(order)
570 }
571
572 #[pyo3(name = "to_dict")]
573 fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
574 let dict = PyDict::new(py);
575 dict.set_item("trader_id", self.trader_id.to_string())?;
576 dict.set_item("strategy_id", self.strategy_id.to_string())?;
577 dict.set_item("instrument_id", self.instrument_id.to_string())?;
578 dict.set_item("client_order_id", self.client_order_id.to_string())?;
579 dict.set_item("side", self.side.to_string())?;
580 dict.set_item("type", self.order_type.to_string())?;
581 dict.set_item("quantity", self.quantity.to_string())?;
582 dict.set_item("status", self.status.to_string())?;
583 self.price.map_or_else(
584 || dict.set_item("price", py.None()),
585 |x| dict.set_item("price", x.to_string()),
586 )?;
587 self.trigger_price.map_or_else(
588 || dict.set_item("trigger_price", py.None()),
589 |x| dict.set_item("trigger_price", x.to_string()),
590 )?;
591 dict.set_item("trigger_type", self.trigger_type.to_string())?;
592 dict.set_item("limit_offset", self.limit_offset.to_string())?;
593 dict.set_item("trailing_offset", self.trailing_offset.to_string())?;
594 dict.set_item(
595 "trailing_offset_type",
596 self.trailing_offset_type.to_string(),
597 )?;
598 dict.set_item("filled_qty", self.filled_qty.to_string())?;
599 dict.set_item("time_in_force", self.time_in_force.to_string())?;
600 dict.set_item("is_post_only", self.is_post_only)?;
601 dict.set_item("is_reduce_only", self.is_reduce_only)?;
602 dict.set_item("is_quote_quantity", self.is_quote_quantity)?;
603 dict.set_item("init_id", self.init_id.to_string())?;
604 dict.set_item(
605 "expire_time_ns",
606 self.expire_time.filter(|&t| t != 0).map(|t| t.as_u64()),
607 )?;
608 dict.set_item("ts_init", self.ts_init.as_u64())?;
609 dict.set_item("ts_last", self.ts_last.as_u64())?;
610 dict.set_item(
611 "commissions",
612 commissions_from_indexmap(py, self.commissions())?,
613 )?;
614 self.activation_price.map_or_else(
615 || dict.set_item("activation_price", py.None()),
616 |x| dict.set_item("activation_price", x.to_string()),
617 )?;
618 self.last_trade_id.map_or_else(
619 || dict.set_item("last_trade_id", py.None()),
620 |x| dict.set_item("last_trade_id", x.to_string()),
621 )?;
622 self.avg_px.map_or_else(
623 || dict.set_item("avg_px", py.None()),
624 |x| dict.set_item("avg_px", x.to_string()),
625 )?;
626 self.position_id.map_or_else(
627 || dict.set_item("position_id", py.None()),
628 |x| dict.set_item("position_id", x.to_string()),
629 )?;
630 self.liquidity_side.map_or_else(
631 || dict.set_item("liquidity_side", py.None()),
632 |x| dict.set_item("liquidity_side", x.to_string()),
633 )?;
634 self.slippage.map_or_else(
635 || dict.set_item("slippage", py.None()),
636 |x| dict.set_item("slippage", x.to_string()),
637 )?;
638 self.account_id.map_or_else(
639 || dict.set_item("account_id", py.None()),
640 |x| dict.set_item("account_id", x.to_string()),
641 )?;
642 self.venue_order_id.map_or_else(
643 || dict.set_item("venue_order_id", py.None()),
644 |x| dict.set_item("venue_order_id", x.to_string()),
645 )?;
646 self.display_qty.map_or_else(
647 || dict.set_item("display_qty", py.None()),
648 |x| dict.set_item("display_qty", x.to_string()),
649 )?;
650 self.emulation_trigger.map_or_else(
651 || dict.set_item("emulation_trigger", py.None()),
652 |x| dict.set_item("emulation_trigger", x.to_string()),
653 )?;
654 self.trigger_instrument_id.map_or_else(
655 || dict.set_item("trigger_instrument_id", py.None()),
656 |x| dict.set_item("trigger_instrument_id", x.to_string()),
657 )?;
658 self.contingency_type.map_or_else(
659 || dict.set_item("contingency_type", py.None()),
660 |x| dict.set_item("contingency_type", x.to_string()),
661 )?;
662 self.order_list_id.map_or_else(
663 || dict.set_item("order_list_id", py.None()),
664 |x| dict.set_item("order_list_id", x.to_string()),
665 )?;
666 dict.set_item(
667 "linked_order_ids",
668 self.linked_order_ids
669 .as_ref()
670 .map(|x| x.iter().map(ToString::to_string).collect::<Vec<String>>()),
671 )?;
672 self.parent_order_id.map_or_else(
673 || dict.set_item("parent_order_id", py.None()),
674 |x| dict.set_item("parent_order_id", x.to_string()),
675 )?;
676 self.exec_algorithm_id.map_or_else(
677 || dict.set_item("exec_algorithm_id", py.None()),
678 |x| dict.set_item("exec_algorithm_id", x.to_string()),
679 )?;
680 dict.set_item(
681 "exec_algorithm_params",
682 self.exec_algorithm_params.as_ref().map(|x| {
683 x.iter()
684 .map(|(k, v)| (k.to_string(), v.to_string()))
685 .collect::<IndexMap<String, String>>()
686 }),
687 )?;
688 self.exec_spawn_id.map_or_else(
689 || dict.set_item("exec_spawn_id", py.None()),
690 |x| dict.set_item("exec_spawn_id", x.to_string()),
691 )?;
692 dict.set_item(
693 "tags",
694 self.tags
695 .as_ref()
696 .map(|vec| vec.iter().map(ToString::to_string).collect::<Vec<String>>()),
697 )?;
698 Ok(dict.into())
699 }
700}