waldiez 0.4.9__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of waldiez might be problematic. Click here for more details.

Files changed (43) hide show
  1. waldiez/__init__.py +1 -2
  2. waldiez/_version.py +1 -1
  3. waldiez/cli.py +65 -58
  4. waldiez/exporter.py +64 -9
  5. waldiez/exporting/agent/extras/group_manager_agent_extas.py +1 -1
  6. waldiez/exporting/core/context.py +12 -0
  7. waldiez/exporting/core/extras/flow_extras.py +2 -14
  8. waldiez/exporting/core/types.py +21 -0
  9. waldiez/exporting/flow/exporter.py +4 -0
  10. waldiez/exporting/flow/factory.py +16 -0
  11. waldiez/exporting/flow/orchestrator.py +12 -0
  12. waldiez/exporting/flow/utils/__init__.py +2 -0
  13. waldiez/exporting/flow/utils/common.py +96 -2
  14. waldiez/exporting/flow/utils/logging.py +5 -6
  15. waldiez/io/mqtt.py +7 -3
  16. waldiez/io/structured.py +5 -1
  17. waldiez/models/common/method_utils.py +1 -1
  18. waldiez/models/tool/tool.py +2 -1
  19. waldiez/runner.py +402 -321
  20. waldiez/running/__init__.py +6 -34
  21. waldiez/running/base_runner.py +907 -0
  22. waldiez/running/environment.py +74 -0
  23. waldiez/running/import_runner.py +424 -0
  24. waldiez/running/patch_io_stream.py +208 -0
  25. waldiez/running/post_run.py +26 -24
  26. waldiez/running/pre_run.py +2 -46
  27. waldiez/running/protocol.py +281 -0
  28. waldiez/running/run_results.py +22 -0
  29. waldiez/running/subprocess_runner.py +100 -0
  30. waldiez/utils/__init__.py +0 -4
  31. waldiez/utils/version.py +4 -2
  32. {waldiez-0.4.9.dist-info → waldiez-0.5.0.dist-info}/METADATA +39 -113
  33. {waldiez-0.4.9.dist-info → waldiez-0.5.0.dist-info}/RECORD +42 -37
  34. waldiez/utils/flaml_warnings.py +0 -17
  35. /waldiez/{utils/cli_extras → cli_extras}/__init__.py +0 -0
  36. /waldiez/{utils/cli_extras → cli_extras}/jupyter.py +0 -0
  37. /waldiez/{utils/cli_extras → cli_extras}/runner.py +0 -0
  38. /waldiez/{utils/cli_extras → cli_extras}/studio.py +0 -0
  39. /waldiez/running/{util.py → utils.py} +0 -0
  40. {waldiez-0.4.9.dist-info → waldiez-0.5.0.dist-info}/WHEEL +0 -0
  41. {waldiez-0.4.9.dist-info → waldiez-0.5.0.dist-info}/entry_points.txt +0 -0
  42. {waldiez-0.4.9.dist-info → waldiez-0.5.0.dist-info}/licenses/LICENSE +0 -0
  43. {waldiez-0.4.9.dist-info → waldiez-0.5.0.dist-info}/licenses/NOTICE.md +0 -0
@@ -0,0 +1,907 @@
1
+ # SPDX-License-Identifier: Apache-2.0.
2
+ # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+
4
+ # pylint: disable=too-many-instance-attributes,unused-argument
5
+ # pylint: disable=too-many-arguments,too-many-positional-arguments
6
+ # pylint: disable=too-many-public-methods,too-many-locals
7
+
8
+ """Base runner for Waldiez workflows."""
9
+
10
+ import sys
11
+ import tempfile
12
+ import threading
13
+ from pathlib import Path
14
+ from types import TracebackType
15
+ from typing import TYPE_CHECKING, Type, Union
16
+
17
+ from anyio.from_thread import start_blocking_portal
18
+ from typing_extensions import Self
19
+
20
+ from waldiez.exporter import WaldiezExporter
21
+ from waldiez.logger import WaldiezLogger, get_logger
22
+ from waldiez.models import Waldiez
23
+ from waldiez.utils import get_waldiez_version
24
+
25
+ from .environment import refresh_environment, reset_env_vars, set_env_vars
26
+ from .post_run import after_run
27
+ from .pre_run import (
28
+ a_install_requirements,
29
+ install_requirements,
30
+ )
31
+ from .protocol import WaldiezRunnerProtocol
32
+ from .utils import (
33
+ a_chdir,
34
+ chdir,
35
+ )
36
+
37
+ if TYPE_CHECKING:
38
+ from autogen import ChatResult # type: ignore[import-untyped]
39
+
40
+
41
+ class WaldiezBaseRunner(WaldiezRunnerProtocol):
42
+ """Base runner for Waldiez.
43
+
44
+ Methods to override:
45
+ - _before_run: Actions to perform before running the flow.
46
+ - _a_before_run: Async actions to perform before running the flow.
47
+ - _run: Actual implementation of the run logic.
48
+ - _a_run: Async implementation of the run logic.
49
+ - _after_run: Actions to perform after running the flow.
50
+ - _a_after_run: Async actions to perform after running the flow.
51
+ - _start: Implementation of non-blocking start logic.
52
+ - _a_start: Async implementation of non-blocking start logic.
53
+ - _stop: Actions to perform when stopping the flow.
54
+ - _a_stop: Async actions to perform when stopping the flow.
55
+ """
56
+
57
+ _threaded: bool
58
+ _structured_io: bool
59
+ _isolated: bool
60
+ _output_path: str | Path | None
61
+ _uploads_root: str | Path | None
62
+ _skip_patch_io: bool
63
+ _running: bool
64
+
65
+ def __init__(
66
+ self,
67
+ waldiez: Waldiez,
68
+ output_path: str | Path | None,
69
+ uploads_root: str | Path | None,
70
+ structured_io: bool,
71
+ isolated: bool,
72
+ threaded: bool,
73
+ skip_patch_io: bool = False,
74
+ ) -> None:
75
+ """Initialize the Waldiez manager."""
76
+ self._waldiez = waldiez
77
+ WaldiezBaseRunner._running = False
78
+ WaldiezBaseRunner._structured_io = structured_io
79
+ WaldiezBaseRunner._isolated = isolated
80
+ WaldiezBaseRunner._output_path = output_path
81
+ WaldiezBaseRunner._uploads_root = uploads_root
82
+ WaldiezBaseRunner._threaded = threaded
83
+ WaldiezBaseRunner._skip_patch_io = skip_patch_io
84
+ self._called_install_requirements = False
85
+ self._exporter = WaldiezExporter(waldiez)
86
+ self._stop_requested = threading.Event()
87
+ self._logger = get_logger()
88
+ self._last_results: Union[
89
+ "ChatResult",
90
+ list["ChatResult"],
91
+ dict[int, "ChatResult"],
92
+ ] = []
93
+ self._last_exception: Exception | None = None
94
+
95
+ def is_running(self) -> bool:
96
+ """Check if the workflow is currently running.
97
+
98
+ Returns
99
+ -------
100
+ bool
101
+ True if the workflow is running, False otherwise.
102
+ """
103
+ return WaldiezBaseRunner._running
104
+
105
+ # ===================================================================
106
+ # PRIVATE METHODS TO OVERRIDE IN SUBCLASSES
107
+ # ===================================================================
108
+ def _before_run(
109
+ self,
110
+ output_file: Path,
111
+ uploads_root: Path | None,
112
+ ) -> Path:
113
+ """Run before the flow execution."""
114
+ self.log.info("Preparing workflow file: %s", output_file)
115
+ temp_dir = Path(tempfile.mkdtemp())
116
+ file_name = output_file.name
117
+ with chdir(to=temp_dir):
118
+ self._exporter.export(
119
+ path=file_name,
120
+ force=True,
121
+ uploads_root=uploads_root,
122
+ # if not isolated, we use structured IO in a context manager
123
+ structured_io=WaldiezBaseRunner._structured_io
124
+ and WaldiezBaseRunner._isolated,
125
+ skip_patch_io=WaldiezBaseRunner._skip_patch_io,
126
+ )
127
+ return temp_dir
128
+
129
+ async def _a_before_run(
130
+ self,
131
+ output_file: Path,
132
+ uploads_root: Path | None,
133
+ ) -> Path:
134
+ """Run before the flow execution asynchronously."""
135
+ temp_dir = Path(tempfile.mkdtemp())
136
+ file_name = output_file.name
137
+ async with a_chdir(to=temp_dir):
138
+ self._exporter.export(
139
+ path=file_name,
140
+ uploads_root=uploads_root,
141
+ structured_io=self.structured_io,
142
+ force=True,
143
+ )
144
+ return temp_dir
145
+
146
+ def _run(
147
+ self,
148
+ temp_dir: Path,
149
+ output_file: Path,
150
+ uploads_root: Path | None,
151
+ skip_mmd: bool,
152
+ ) -> Union[
153
+ "ChatResult",
154
+ list["ChatResult"],
155
+ dict[int, "ChatResult"],
156
+ ]: # pyright: ignore
157
+ """Run the Waldiez flow."""
158
+ raise NotImplementedError(
159
+ "The _run method must be implemented in the subclass."
160
+ )
161
+
162
+ async def _a_run(
163
+ self,
164
+ temp_dir: Path,
165
+ output_file: Path,
166
+ uploads_root: Path | None,
167
+ skip_mmd: bool,
168
+ ) -> Union[
169
+ "ChatResult",
170
+ list["ChatResult"],
171
+ dict[int, "ChatResult"],
172
+ ]: # pyright: ignore
173
+ """Run the Waldiez flow asynchronously."""
174
+ raise NotImplementedError(
175
+ "The _a_run method must be implemented in the subclass."
176
+ )
177
+
178
+ def _start(
179
+ self,
180
+ temp_dir: Path,
181
+ output_file: Path,
182
+ uploads_root: Path | None,
183
+ skip_mmd: bool,
184
+ ) -> None:
185
+ """Start running the Waldiez flow in a non-blocking way."""
186
+ raise NotImplementedError(
187
+ "The _start method must be implemented in the subclass."
188
+ )
189
+
190
+ async def _a_start(
191
+ self,
192
+ temp_dir: Path,
193
+ output_file: Path,
194
+ uploads_root: Path | None,
195
+ skip_mmd: bool,
196
+ ) -> None:
197
+ """Start running the Waldiez flow in a non-blocking way asynchronously.
198
+
199
+ Parameters
200
+ ----------
201
+ temp_dir : Path
202
+ The path to the temporary directory created for the run.
203
+ output_file : Path
204
+ The path to the output file.
205
+ uploads_root : Path | None
206
+ The root path for uploads, if any.
207
+ structured_io : bool
208
+ Whether to use structured IO instead of the default 'input/print'.
209
+ skip_mmd : bool
210
+ Whether to skip generating the mermaid diagram.
211
+ """
212
+ raise NotImplementedError(
213
+ "The _a_start method must be implemented in the subclass."
214
+ )
215
+
216
+ def _after_run(
217
+ self,
218
+ results: Union[
219
+ "ChatResult",
220
+ list["ChatResult"],
221
+ dict[int, "ChatResult"],
222
+ ],
223
+ output_file: Path,
224
+ uploads_root: Path | None,
225
+ temp_dir: Path,
226
+ skip_mmd: bool,
227
+ ) -> None:
228
+ """Run after the flow execution."""
229
+ # Save results
230
+ self._last_results = results
231
+
232
+ # Reset stop flag for next run
233
+ self._stop_requested.clear()
234
+ after_run(
235
+ temp_dir=temp_dir,
236
+ output_file=output_file,
237
+ flow_name=self.waldiez.name,
238
+ uploads_root=uploads_root,
239
+ skip_mmd=skip_mmd,
240
+ )
241
+ self.log.info("Cleanup completed")
242
+
243
+ async def _a_after_run(
244
+ self,
245
+ results: Union[
246
+ "ChatResult",
247
+ list["ChatResult"],
248
+ dict[int, "ChatResult"],
249
+ ],
250
+ output_file: Path,
251
+ uploads_root: Path | None,
252
+ temp_dir: Path,
253
+ skip_mmd: bool,
254
+ ) -> None:
255
+ """Run after the flow execution asynchronously."""
256
+ self._after_run(
257
+ results=results,
258
+ output_file=output_file,
259
+ uploads_root=uploads_root,
260
+ temp_dir=temp_dir,
261
+ skip_mmd=skip_mmd,
262
+ )
263
+
264
+ def _stop(self) -> None:
265
+ """Actions to perform when stopping the flow."""
266
+ raise NotImplementedError(
267
+ "The _stop method must be implemented in the subclass."
268
+ )
269
+
270
+ async def _a_stop(self) -> None:
271
+ """Asynchronously perform actions when stopping the flow."""
272
+ raise NotImplementedError(
273
+ "The _a_stop method must be implemented in the subclass."
274
+ )
275
+
276
+ # ===================================================================
277
+ # HELPER METHODS
278
+ # ===================================================================
279
+ @staticmethod
280
+ def _prepare_paths(
281
+ output_path: str | Path | None = None,
282
+ uploads_root: str | Path | None = None,
283
+ ) -> tuple[Path, Path | None]:
284
+ """Prepare the output and uploads paths."""
285
+ uploads_root_path: Path | None = None
286
+ if uploads_root is not None:
287
+ uploads_root_path = Path(uploads_root)
288
+ WaldiezBaseRunner._uploads_root = uploads_root_path
289
+
290
+ if output_path is not None:
291
+ output_path = Path(output_path)
292
+ WaldiezBaseRunner._output_path = output_path
293
+ if not WaldiezBaseRunner._output_path:
294
+ WaldiezBaseRunner._output_path = Path.cwd() / "waldiez_flow.py"
295
+ output_file: Path = Path(WaldiezBaseRunner._output_path)
296
+ return output_file, uploads_root_path
297
+
298
+ def gather_requirements(self) -> set[str]:
299
+ """Gather extra requirements to install before running the flow.
300
+
301
+ Returns
302
+ -------
303
+ set[str]
304
+ A set of requirements that are not already installed and do not
305
+ include 'waldiez' in their name.
306
+ """
307
+ extra_requirements = {
308
+ req
309
+ for req in self.waldiez.requirements
310
+ if req not in sys.modules and "waldiez" not in req
311
+ }
312
+ waldiez_version = get_waldiez_version()
313
+ if "waldiez" not in sys.modules:
314
+ extra_requirements.add(f"waldiez=={waldiez_version}")
315
+ return extra_requirements
316
+
317
+ def install_requirements(self) -> None:
318
+ """Install the requirements for the flow."""
319
+ if not self._called_install_requirements:
320
+ self._called_install_requirements = True
321
+ extra_requirements = self.gather_requirements()
322
+ if extra_requirements:
323
+ install_requirements(extra_requirements)
324
+
325
+ async def a_install_requirements(self) -> None:
326
+ """Install the requirements for the flow asynchronously."""
327
+ if not self._called_install_requirements:
328
+ self._called_install_requirements = True
329
+ extra_requirements = self.gather_requirements()
330
+ if extra_requirements:
331
+ await a_install_requirements(extra_requirements)
332
+
333
+ # ===================================================================
334
+ # PUBLIC PROTOCOL IMPLEMENTATION
335
+ # ===================================================================
336
+
337
+ def before_run(
338
+ self,
339
+ output_file: Path,
340
+ uploads_root: Path | None,
341
+ ) -> Path:
342
+ """Run the before_run method synchronously.
343
+
344
+ Parameters
345
+ ----------
346
+ output_file : Path
347
+ The path to the output file.
348
+ uploads_root : Path | None
349
+ The root path for uploads, if any.
350
+
351
+ Returns
352
+ -------
353
+ Path
354
+ The path to the temporary directory created before running the flow.
355
+ """
356
+ return self._before_run(
357
+ output_file=output_file,
358
+ uploads_root=uploads_root,
359
+ )
360
+
361
+ async def a_before_run(
362
+ self,
363
+ output_file: Path,
364
+ uploads_root: Path | None,
365
+ ) -> Path:
366
+ """Run the _a_before_run method asynchronously.
367
+
368
+ Parameters
369
+ ----------
370
+ output_file : Path
371
+ The path to the output file.
372
+ uploads_root : Path | None
373
+ The root path for uploads, if any.
374
+
375
+ Returns
376
+ -------
377
+ Path
378
+ The path to the temporary directory created before running the flow.
379
+ """
380
+ return await self._a_before_run(
381
+ output_file=output_file,
382
+ uploads_root=uploads_root,
383
+ )
384
+
385
+ def run(
386
+ self,
387
+ output_path: str | Path | None = None,
388
+ uploads_root: str | Path | None = None,
389
+ structured_io: bool | None = None,
390
+ threaded: bool | None = None,
391
+ skip_patch_io: bool | None = None,
392
+ skip_mmd: bool = False,
393
+ ) -> Union[
394
+ "ChatResult",
395
+ list["ChatResult"],
396
+ dict[int, "ChatResult"],
397
+ ]: # pyright: ignore
398
+ """Run the Waldiez flow in blocking mode.
399
+
400
+ Parameters
401
+ ----------
402
+ output_path : str | Path | None
403
+ The output path, by default None.
404
+ uploads_root : str | Path | None
405
+ The runtime uploads root, by default None.
406
+ structured_io : bool
407
+ Whether to use structured IO instead of the default 'input/print',
408
+ by default False.
409
+ threaded : bool | None
410
+ Whether to run the flow in a threaded environment, by default None.
411
+ skip_mmd : bool
412
+ Whether to skip generating the mermaid diagram, by default False.
413
+ skip_patch_io : bool | None
414
+ Whether to skip patching the IO streams, by default None.
415
+
416
+ Returns
417
+ -------
418
+ Union[ChatResult, list[ChatResult], dict[int, ChatResult]]
419
+ The result of the run, which can be a single ChatResult,
420
+ a list of ChatResults,
421
+ or a dictionary mapping indices to ChatResults.
422
+
423
+ Raises
424
+ ------
425
+ RuntimeError
426
+ If the runner is already running.
427
+ """
428
+ if skip_patch_io is not None:
429
+ WaldiezBaseRunner._skip_patch_io = skip_patch_io
430
+ if structured_io is not None:
431
+ WaldiezBaseRunner._structured_io = structured_io
432
+ if threaded is not None:
433
+ WaldiezBaseRunner._threaded = threaded
434
+ if self.is_running():
435
+ raise RuntimeError("Workflow already running")
436
+ if self.waldiez.is_async:
437
+ with start_blocking_portal(backend="asyncio") as portal:
438
+ return portal.call(
439
+ self.a_run,
440
+ output_path,
441
+ uploads_root,
442
+ structured_io,
443
+ skip_patch_io,
444
+ skip_mmd,
445
+ )
446
+ output_file, uploads_root_path = self._prepare_paths(
447
+ output_path=output_path,
448
+ uploads_root=uploads_root,
449
+ )
450
+ temp_dir = self.before_run(
451
+ output_file=output_file,
452
+ uploads_root=uploads_root_path,
453
+ )
454
+ self.install_requirements()
455
+ refresh_environment()
456
+ WaldiezBaseRunner._running = True
457
+ results: Union[
458
+ "ChatResult",
459
+ list["ChatResult"],
460
+ dict[int, "ChatResult"],
461
+ ] = []
462
+ old_env_vars = set_env_vars(self.waldiez.get_flow_env_vars())
463
+ try:
464
+ with chdir(to=temp_dir):
465
+ sys.path.insert(0, str(temp_dir))
466
+ results = self._run(
467
+ temp_dir=temp_dir,
468
+ output_file=output_file,
469
+ uploads_root=uploads_root_path,
470
+ skip_mmd=skip_mmd,
471
+ )
472
+ finally:
473
+ WaldiezBaseRunner._running = False
474
+ reset_env_vars(old_env_vars)
475
+ self.after_run(
476
+ results=results,
477
+ output_file=output_file,
478
+ uploads_root=uploads_root_path,
479
+ temp_dir=temp_dir,
480
+ skip_mmd=skip_mmd,
481
+ )
482
+ if sys.path[0] == str(temp_dir):
483
+ sys.path.pop(0)
484
+ return results
485
+
486
+ async def a_run(
487
+ self,
488
+ output_path: str | Path | None = None,
489
+ uploads_root: str | Path | None = None,
490
+ structured_io: bool | None = None,
491
+ skip_patch_io: bool | None = None,
492
+ skip_mmd: bool = False,
493
+ ) -> Union[
494
+ "ChatResult",
495
+ list["ChatResult"],
496
+ dict[int, "ChatResult"],
497
+ ]: # pyright: ignore
498
+ """Run the Waldiez flow asynchronously.
499
+
500
+ Parameters
501
+ ----------
502
+ output_path : str | Path | None
503
+ The output path, by default None.
504
+ uploads_root : str | Path | None
505
+ The runtime uploads root, by default None.
506
+ structured_io : bool
507
+ Whether to use structured IO instead of the default 'input/print',
508
+ by default False.
509
+ skip_patch_io : bool | None
510
+ Whether to skip patching I/O, by default None.
511
+ skip_mmd : bool
512
+ Whether to skip generating the mermaid diagram, by default False.
513
+
514
+ Returns
515
+ -------
516
+ Union[ChatResult, list[ChatResult], dict[int, ChatResult]]
517
+ The result of the run, which can be a single ChatResult,
518
+ a list of ChatResults,
519
+ or a dictionary mapping indices to ChatResults.
520
+
521
+ Raises
522
+ ------
523
+ RuntimeError
524
+ If the runner is already running.
525
+ """
526
+ if skip_patch_io is not None:
527
+ WaldiezBaseRunner._skip_patch_io = skip_patch_io
528
+ if structured_io is not None:
529
+ WaldiezBaseRunner._structured_io = structured_io
530
+ if self.is_running():
531
+ raise RuntimeError("Workflow already running")
532
+ output_file, uploads_root_path = self._prepare_paths(
533
+ output_path=output_path,
534
+ uploads_root=uploads_root,
535
+ )
536
+ temp_dir = await self._a_before_run(
537
+ output_file=output_file,
538
+ uploads_root=uploads_root_path,
539
+ )
540
+ await self.a_install_requirements()
541
+ refresh_environment()
542
+ WaldiezBaseRunner._running = True
543
+ results: Union[
544
+ "ChatResult",
545
+ list["ChatResult"],
546
+ dict[int, "ChatResult"],
547
+ ] = []
548
+ try:
549
+ async with a_chdir(to=temp_dir):
550
+ sys.path.insert(0, str(temp_dir))
551
+ results = await self._a_run(
552
+ temp_dir=temp_dir,
553
+ output_file=output_file,
554
+ uploads_root=uploads_root_path,
555
+ skip_mmd=skip_mmd,
556
+ )
557
+ finally:
558
+ WaldiezBaseRunner._running = False
559
+ await self._a_after_run(
560
+ results=results,
561
+ output_file=output_file,
562
+ uploads_root=uploads_root_path,
563
+ temp_dir=temp_dir,
564
+ skip_mmd=skip_mmd,
565
+ )
566
+ if sys.path[0] == str(temp_dir):
567
+ sys.path.pop(0)
568
+ return results
569
+
570
+ def start(
571
+ self,
572
+ output_path: str | Path | None,
573
+ uploads_root: str | Path | None,
574
+ structured_io: bool | None = None,
575
+ skip_patch_io: bool | None = None,
576
+ skip_mmd: bool = False,
577
+ ) -> None:
578
+ """Start running the Waldiez flow in a non-blocking way.
579
+
580
+ Parameters
581
+ ----------
582
+ output_path : str | Path | None
583
+ The output path.
584
+ uploads_root : str | Path | None
585
+ The runtime uploads root.
586
+ structured_io : bool | None
587
+ Whether to use structured IO instead of the default 'input/print'.
588
+ skip_patch_io : bool | None
589
+ Whether to skip patching I/O, by default None.
590
+ skip_mmd : bool
591
+ Whether to skip generating the mermaid diagram, by default False.
592
+
593
+ Raises
594
+ ------
595
+ RuntimeError
596
+ If the runner is already running.
597
+ """
598
+ if skip_patch_io is not None:
599
+ WaldiezBaseRunner._skip_patch_io = skip_patch_io
600
+ if structured_io is not None:
601
+ WaldiezBaseRunner._structured_io = structured_io
602
+ if self.is_running():
603
+ raise RuntimeError("Workflow already running")
604
+ output_file, uploads_root_path = self._prepare_paths(
605
+ output_path=output_path,
606
+ uploads_root=uploads_root,
607
+ )
608
+ temp_dir = self.before_run(
609
+ output_file=output_file,
610
+ uploads_root=uploads_root_path,
611
+ )
612
+ self.install_requirements()
613
+ refresh_environment()
614
+ WaldiezBaseRunner._running = True
615
+ self._start(
616
+ temp_dir=temp_dir,
617
+ output_file=output_file,
618
+ uploads_root=uploads_root_path,
619
+ skip_mmd=skip_mmd,
620
+ )
621
+
622
+ async def a_start(
623
+ self,
624
+ output_path: str | Path | None,
625
+ uploads_root: str | Path | None,
626
+ structured_io: bool | None = None,
627
+ skip_patch_io: bool | None = None,
628
+ skip_mmd: bool = False,
629
+ ) -> None:
630
+ """Asynchronously start running the Waldiez flow in a non-blocking way.
631
+
632
+ Parameters
633
+ ----------
634
+ output_path : str | Path | None
635
+ The output path.
636
+ uploads_root : str | Path | None
637
+ The runtime uploads root.
638
+ structured_io : bool | None = None
639
+ Whether to use structured IO instead of the default 'input/print'.
640
+ skip_patch_io : bool | None = None
641
+ Whether to skip patching I/O, by default None.
642
+ skip_mmd : bool | None = None
643
+ Whether to skip generating the mermaid diagram, by default None.
644
+
645
+ Raises
646
+ ------
647
+ RuntimeError
648
+ If the runner is already running.
649
+ """
650
+ if skip_patch_io is not None:
651
+ WaldiezBaseRunner._skip_patch_io = skip_patch_io
652
+ if structured_io is not None:
653
+ WaldiezBaseRunner._structured_io = structured_io
654
+ if self.is_running():
655
+ raise RuntimeError("Workflow already running")
656
+ output_file, uploads_root_path = self._prepare_paths(
657
+ output_path=output_path,
658
+ uploads_root=uploads_root,
659
+ )
660
+ temp_dir = await self._a_before_run(
661
+ output_file=output_file,
662
+ uploads_root=uploads_root_path,
663
+ )
664
+ await self.a_install_requirements()
665
+ refresh_environment()
666
+ WaldiezBaseRunner._running = True
667
+ await self._a_start(
668
+ temp_dir=temp_dir,
669
+ output_file=output_file,
670
+ uploads_root=uploads_root_path,
671
+ skip_mmd=skip_mmd,
672
+ )
673
+
674
+ def after_run(
675
+ self,
676
+ results: Union[
677
+ "ChatResult",
678
+ list["ChatResult"],
679
+ dict[int, "ChatResult"],
680
+ ],
681
+ output_file: Path,
682
+ uploads_root: Path | None,
683
+ temp_dir: Path,
684
+ skip_mmd: bool,
685
+ ) -> None:
686
+ """Actions to perform after running the flow.
687
+
688
+ Parameters
689
+ ----------
690
+ results : Union[ChatResult, list[ChatResult], dict[int, ChatResult]]
691
+ The results of the flow run.
692
+ output_file : Path
693
+ The path to the output file.
694
+ uploads_root : Path | None
695
+ The root path for uploads, if any.
696
+ temp_dir : Path
697
+ The path to the temporary directory used during the run.
698
+ skip_mmd : bool
699
+ Whether to skip generating the mermaid diagram.
700
+ """
701
+ self._after_run(
702
+ results=results,
703
+ output_file=output_file,
704
+ uploads_root=uploads_root,
705
+ temp_dir=temp_dir,
706
+ skip_mmd=skip_mmd,
707
+ )
708
+
709
+ async def a_after_run(
710
+ self,
711
+ results: Union[
712
+ "ChatResult",
713
+ list["ChatResult"],
714
+ dict[int, "ChatResult"],
715
+ ],
716
+ output_file: Path,
717
+ uploads_root: Path | None,
718
+ temp_dir: Path,
719
+ skip_mmd: bool,
720
+ ) -> None:
721
+ """Asynchronously perform actions after running the flow.
722
+
723
+ Parameters
724
+ ----------
725
+ results : Union[ChatResult, list[ChatResult], dict[int, ChatResult]]
726
+ The results of the flow run.
727
+ output_file : Path
728
+ The path to the output file.
729
+ uploads_root : Path | None
730
+ The root path for uploads, if any.
731
+ temp_dir : Path
732
+ The path to the temporary directory used during the run.
733
+ skip_mmd : bool
734
+ Whether to skip generating the mermaid diagram.
735
+ """
736
+ await self._a_after_run(
737
+ results=results,
738
+ output_file=output_file,
739
+ uploads_root=uploads_root,
740
+ temp_dir=temp_dir,
741
+ skip_mmd=skip_mmd,
742
+ )
743
+
744
+ def stop(self) -> None:
745
+ """Stop the runner if it is running."""
746
+ if not self.is_running():
747
+ return
748
+ try:
749
+ self._stop()
750
+ finally:
751
+ WaldiezBaseRunner._running = False
752
+
753
+ async def a_stop(self) -> None:
754
+ """Asynchronously stop the runner if it is running."""
755
+ if not self.is_running():
756
+ return
757
+ try:
758
+ await self._a_stop()
759
+ finally:
760
+ WaldiezBaseRunner._running = False
761
+
762
+ # ===================================================================
763
+ # PROPERTIES AND CONTEXT MANAGERS
764
+ # ===================================================================
765
+
766
+ @property
767
+ def waldiez(self) -> Waldiez:
768
+ """Get the Waldiez instance."""
769
+ return self._waldiez
770
+
771
+ @property
772
+ def is_async(self) -> bool:
773
+ """Check if the workflow is async."""
774
+ return self.waldiez.is_async
775
+
776
+ @property
777
+ def running(self) -> bool:
778
+ """Get the running status."""
779
+ return self.is_running()
780
+
781
+ @property
782
+ def log(self) -> WaldiezLogger:
783
+ """Get the logger for the runner."""
784
+ return self._logger
785
+
786
+ @property
787
+ def threaded(self) -> bool:
788
+ """Check if the runner is running in a threaded environment."""
789
+ return WaldiezBaseRunner._threaded
790
+
791
+ @property
792
+ def structured_io(self) -> bool:
793
+ """Check if the runner is using structured IO."""
794
+ return WaldiezBaseRunner._structured_io
795
+
796
+ @property
797
+ def isolated(self) -> bool:
798
+ """Check if the runner is running in an isolated environment."""
799
+ return WaldiezBaseRunner._isolated
800
+
801
+ @property
802
+ def output_path(self) -> str | Path | None:
803
+ """Get the output path for the runner."""
804
+ return WaldiezBaseRunner._output_path
805
+
806
+ @property
807
+ def uploads_root(self) -> str | Path | None:
808
+ """Get the uploads root path for the runner."""
809
+ return WaldiezBaseRunner._uploads_root
810
+
811
+ @property
812
+ def skip_patch_io(self) -> bool:
813
+ """Check if the runner is skipping patching IO."""
814
+ return WaldiezBaseRunner._skip_patch_io
815
+
816
+ @classmethod
817
+ def load(
818
+ cls,
819
+ waldiez_file: str | Path,
820
+ name: str | None = None,
821
+ description: str | None = None,
822
+ tags: list[str] | None = None,
823
+ requirements: list[str] | None = None,
824
+ output_path: str | Path | None = None,
825
+ uploads_root: str | Path | None = None,
826
+ structured_io: bool = False,
827
+ isolated: bool = False,
828
+ threaded: bool = False,
829
+ skip_patch_io: bool = True,
830
+ ) -> "WaldiezBaseRunner":
831
+ """Load a waldiez flow from a file and create a runner.
832
+
833
+ Parameters
834
+ ----------
835
+ waldiez_file : str | Path
836
+ The path to the waldiez file.
837
+ name : str | None, optional
838
+ The name of the flow, by default None.
839
+ description : str | None, optional
840
+ The description of the flow, by default None.
841
+ tags : list[str] | None, optional
842
+ The tags for the flow, by default None.
843
+ requirements : list[str] | None, optional
844
+ The requirements for the flow, by default None.
845
+ output_path : str | Path | None, optional
846
+ The path to save the output file, by default None.
847
+ uploads_root : str | Path | None, optional
848
+ The root path for uploads, by default None.
849
+ structured_io : bool, optional
850
+ Whether to use structured IO instead of the default 'input/print',
851
+ by default False.
852
+ isolated : bool, optional
853
+ Whether to run the flow in an isolated environment, default False.
854
+ threaded : bool, optional
855
+ Whether to run the flow in a threaded environment, default False.
856
+ skip_patch_io : bool, optional
857
+ Whether to skip patching IO, by default True.
858
+
859
+ Returns
860
+ -------
861
+ WaldiezBaseRunner
862
+ An instance of WaldiezBaseRunner initialized with the loaded flow.
863
+ """
864
+ waldiez = Waldiez.load(
865
+ waldiez_file,
866
+ name=name,
867
+ description=description,
868
+ tags=tags,
869
+ requirements=requirements,
870
+ )
871
+ return cls(
872
+ waldiez=waldiez,
873
+ output_path=output_path,
874
+ uploads_root=uploads_root,
875
+ structured_io=structured_io,
876
+ isolated=isolated,
877
+ threaded=threaded,
878
+ skip_patch_io=skip_patch_io,
879
+ )
880
+
881
+ def __enter__(self) -> Self:
882
+ """Enter the context manager."""
883
+ return self
884
+
885
+ async def __aenter__(self) -> Self:
886
+ """Enter the context manager asynchronously."""
887
+ return self
888
+
889
+ def __exit__(
890
+ self,
891
+ exc_type: Type[BaseException],
892
+ exc_value: BaseException,
893
+ traceback: TracebackType,
894
+ ) -> None:
895
+ """Exit the context manager."""
896
+ if self.is_running():
897
+ self.stop()
898
+
899
+ async def __aexit__(
900
+ self,
901
+ exc_type: Type[BaseException],
902
+ exc_value: BaseException,
903
+ traceback: TracebackType,
904
+ ) -> None:
905
+ """Exit the context manager asynchronously."""
906
+ if self.is_running():
907
+ await self.a_stop()