waldiez 0.4.8__py3-none-any.whl → 0.4.9__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.

waldiez/_version.py CHANGED
@@ -5,4 +5,4 @@
5
5
  This file is automatically generated by Hatchling.
6
6
  Do not edit this file directly.
7
7
  """
8
- __version__ = VERSION = "0.4.8"
8
+ __version__ = VERSION = "0.4.9"
waldiez/cli.py CHANGED
@@ -2,43 +2,25 @@
2
2
  # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
3
  # flake8: noqa: E501
4
4
  # pylint: disable=missing-function-docstring,missing-param-doc,missing-raises-doc
5
+ # pylint: disable=line-too-long
5
6
  """Command line interface to convert or run a waldiez file."""
6
7
 
7
8
  import json
8
9
  import os
9
10
  from pathlib import Path
10
- from typing import TYPE_CHECKING, Any, Optional, Union
11
+ from typing import Any, Optional
11
12
 
12
13
  import anyio
13
14
  import typer
14
15
  from dotenv import load_dotenv
15
16
  from typing_extensions import Annotated
16
17
 
17
- # pylint: disable=import-error,line-too-long
18
- # pyright: reportMissingImports=false
19
- try: # pragma: no cover
20
- # fmt: off
21
- from ._version import __version__ # type: ignore[unused-ignore, unused-import, import-not-found, import-untyped] # noqa
22
- # fmt: on
23
- except ImportError: # pragma: no cover
24
- import warnings
25
-
26
- warnings.warn(
27
- "Importing __version__ failed. Using 'dev' as version.", stacklevel=2
28
- )
29
- __version__ = "dev"
30
-
31
-
32
18
  from .exporter import WaldiezExporter
33
19
  from .io import StructuredIOStream
34
20
  from .logger import get_logger
35
21
  from .models import Waldiez
36
22
  from .runner import WaldiezRunner
37
- from .utils import add_cli_extras
38
-
39
- if TYPE_CHECKING:
40
- from autogen import ChatResult # type: ignore[import-untyped]
41
-
23
+ from .utils import add_cli_extras, get_waldiez_version
42
24
 
43
25
  load_dotenv()
44
26
  LOG = get_logger()
@@ -71,7 +53,7 @@ def show_version(
71
53
  ) -> None:
72
54
  """Show the version of the Waldiez package and exit."""
73
55
  if version:
74
- typer.echo(f"waldiez version: {__version__}")
56
+ typer.echo(f"waldiez version: {get_waldiez_version()}")
75
57
  raise typer.Exit()
76
58
 
77
59
 
@@ -130,27 +112,12 @@ def run(
130
112
  except json.decoder.JSONDecodeError as error:
131
113
  typer.echo("Invalid .waldiez file. Not a valid json?")
132
114
  raise typer.Exit(code=1) from error
133
- results = _do_run(
115
+ _do_run(
134
116
  data,
135
117
  structured=structured,
136
118
  uploads_root=uploads_root,
137
119
  output_path=output_path,
138
120
  )
139
- if isinstance(results, list):
140
- LOG.info("Results:")
141
- for result in results:
142
- _log_result(result)
143
- sep = "-" * 80
144
- print("\n" + f"{sep}" + "\n")
145
- elif isinstance(results, dict):
146
- LOG.info("Results:")
147
- for key, result in results.items():
148
- LOG.info("Order: %s", key)
149
- _log_result(result)
150
- sep = "-" * 80
151
- print("\n" + f"{sep}" + "\n")
152
- else:
153
- _log_result(results)
154
121
 
155
122
 
156
123
  @app.command()
@@ -239,7 +206,7 @@ def _do_run(
239
206
  structured: bool,
240
207
  uploads_root: Optional[Path],
241
208
  output_path: Optional[Path],
242
- ) -> Union["ChatResult", list["ChatResult"], dict[int, "ChatResult"]]:
209
+ ) -> None:
243
210
  """Run the Waldiez flow and get the results."""
244
211
  waldiez = Waldiez.from_dict(data)
245
212
  if structured:
@@ -247,30 +214,29 @@ def _do_run(
247
214
  with StructuredIOStream.set_default(stream):
248
215
  runner = WaldiezRunner(waldiez)
249
216
  if waldiez.is_async:
250
- results = anyio.run(
217
+ anyio.run(
251
218
  runner.a_run,
252
219
  output_path,
253
220
  uploads_root,
254
221
  )
255
222
  else:
256
- results = runner.run(
223
+ runner.run(
257
224
  output_path=output_path,
258
225
  uploads_root=uploads_root,
259
226
  )
260
227
  else:
261
228
  runner = WaldiezRunner(waldiez)
262
229
  if waldiez.is_async:
263
- results = anyio.run(
230
+ anyio.run(
264
231
  runner.a_run,
265
232
  output_path,
266
233
  uploads_root,
267
234
  )
268
235
  else:
269
- results = runner.run(
236
+ runner.run(
270
237
  output_path=output_path,
271
238
  uploads_root=uploads_root,
272
239
  )
273
- return results
274
240
 
275
241
 
276
242
  def _get_output_path(output: Optional[Path], force: bool) -> Optional[Path]:
@@ -286,16 +252,6 @@ def _get_output_path(output: Optional[Path], force: bool) -> Optional[Path]:
286
252
  return output
287
253
 
288
254
 
289
- def _log_result(result: "ChatResult") -> None:
290
- """Log the result of the Waldiez flow."""
291
- LOG.info("Chat History:\n")
292
- LOG.info(result.chat_history)
293
- LOG.info("Summary:\n")
294
- LOG.info(result.summary)
295
- LOG.info("Cost:\n")
296
- LOG.info(result.cost)
297
-
298
-
299
255
  add_cli_extras(app)
300
256
 
301
257
  if __name__ == "__main__":
@@ -65,15 +65,13 @@ def _get_chroma_embedding_function_string(
65
65
  to_import = ""
66
66
  embedding_function_arg = ""
67
67
  embedding_function_content = ""
68
- vector_db_model = agent.retrieve_config.db_config.model
69
68
  if not agent.retrieve_config.use_custom_embedding:
70
69
  to_import = (
71
70
  "from chromadb.utils.embedding_functions."
72
71
  "sentence_transformer_embedding_function import "
73
72
  "SentenceTransformerEmbeddingFunction"
74
73
  )
75
- embedding_function_arg = "SentenceTransformerEmbeddingFunction("
76
- embedding_function_arg += f'model_name="{vector_db_model}")'
74
+ embedding_function_arg = f"{agent_name}_embedding_function"
77
75
  else:
78
76
  embedding_function_content, embedding_function_arg = (
79
77
  agent.retrieve_config.get_custom_embedding_function(
@@ -124,21 +122,35 @@ def get_chroma_db_args(
124
122
  # manually initializing the collection before running the flow,
125
123
  # might be a workaround.
126
124
  content_before = f"{agent_name}_client = {client_str}" + "\n"
125
+ vector_db_model = agent.retrieve_config.db_config.model
126
+ if not embedding_function_body:
127
+ content_before += (
128
+ f"{agent_name}_embedding_function = "
129
+ "SentenceTransformerEmbeddingFunction(\n"
130
+ f' model_name="{vector_db_model}",' + "\n"
131
+ ")\n"
132
+ )
127
133
  collection_name = agent.retrieve_config.collection_name
128
134
  get_or_create = agent.retrieve_config.get_or_create
129
135
  if collection_name:
130
136
  if get_or_create:
131
137
  content_before += (
132
- f"{agent_name}_client.get_or_create_collection("
133
- f'"{collection_name}")' + "\n"
138
+ f"{agent_name}_client.get_or_create_collection" + "(\n"
139
+ f' "{collection_name}",' + "\n"
140
+ f" embedding_function={embedding_function_arg}," + "\n"
141
+ ")" + "\n"
134
142
  )
135
143
  else:
136
144
  content_before += (
137
145
  "try:\n"
138
- f' {agent_name}_client.get_collection("{collection_name}")'
139
- + "\n"
146
+ f" {agent_name}_client.get_collection(" + "\n"
147
+ f' "{collection_name}",' + "\n"
148
+ f" embedding_function={embedding_function_arg}," + "\n"
149
+ " )\n"
140
150
  "except ValueError:\n"
141
- f" {agent_name}_client.create_collection("
142
- f'"{collection_name}")' + "\n"
151
+ f" {agent_name}_client.create_collection(" + "\n"
152
+ f' "{collection_name}",' + "\n"
153
+ f" embedding_function={embedding_function_arg}," + "\n"
154
+ " )\n"
143
155
  )
144
156
  return kwarg_string, to_import, embedding_function_body, content_before
waldiez/io/structured.py CHANGED
@@ -36,9 +36,13 @@ class StructuredIOStream(IOStream):
36
36
  uploads_root: Path | None = None
37
37
 
38
38
  def __init__(
39
- self, timeout: float = 120, uploads_root: Path | str | None = None
39
+ self,
40
+ timeout: float = 120,
41
+ uploads_root: Path | str | None = None,
42
+ is_async: bool = False,
40
43
  ) -> None:
41
44
  self.timeout = timeout
45
+ self.is_async = is_async
42
46
  if uploads_root is not None:
43
47
  self.uploads_root = Path(uploads_root).resolve()
44
48
  if not self.uploads_root.exists():
@@ -106,6 +110,22 @@ class StructuredIOStream(IOStream):
106
110
  uploads_root=self.uploads_root,
107
111
  base_name=request_id,
108
112
  )
113
+ # let's keep an eye here:
114
+ # https://github.com/ag2ai/ag2/blob/main/autogen/agentchat/conversable_agent.py#L2973
115
+ # reply = await iostream.input(prompt) ???? (await???)
116
+ if self.is_async:
117
+ # let's make a coroutine to just return the user response
118
+ async def async_input() -> str:
119
+ """Asynchronous input method.
120
+
121
+ Returns
122
+ -------
123
+ str
124
+ The line read from the input stream.
125
+ """
126
+ return user_response
127
+
128
+ return async_input() # type: ignore
109
129
  return user_response
110
130
 
111
131
  # noinspection PyMethodMayBeStatic
waldiez/runner.py CHANGED
@@ -18,7 +18,6 @@ from pathlib import Path
18
18
  from types import ModuleType, TracebackType
19
19
  from typing import (
20
20
  TYPE_CHECKING,
21
- Callable,
22
21
  Optional,
23
22
  Set,
24
23
  Type,
@@ -34,7 +33,6 @@ from .running import (
34
33
  after_run,
35
34
  before_run,
36
35
  chdir,
37
- get_printer,
38
36
  install_requirements,
39
37
  refresh_environment,
40
38
  reset_env_vars,
@@ -166,25 +164,22 @@ class WaldiezRunner:
166
164
  def install_requirements(self) -> None:
167
165
  """Install the requirements for the flow."""
168
166
  self._called_install_requirements = True
169
- printer = get_printer()
170
167
  extra_requirements = self.gather_requirements()
171
168
  if extra_requirements:
172
- install_requirements(extra_requirements, printer)
169
+ install_requirements(extra_requirements)
173
170
 
174
171
  async def a_install_requirements(self) -> None:
175
172
  """Install the requirements for the flow asynchronously."""
176
173
  self._called_install_requirements = True
177
- printer = get_printer()
178
174
  extra_requirements = self.gather_requirements()
179
175
  if extra_requirements:
180
- await a_install_requirements(extra_requirements, printer)
176
+ await a_install_requirements(extra_requirements)
181
177
 
182
178
  def _before_run(
183
179
  self,
184
180
  temp_dir: Path,
185
181
  file_name: str,
186
182
  module_name: str,
187
- printer: Callable[..., None],
188
183
  ) -> tuple[ModuleType, dict[str, str]]:
189
184
  self._exporter.export(Path(file_name))
190
185
  # unique_names = self._exporter.context.get_unique_names()
@@ -197,8 +192,8 @@ class WaldiezRunner:
197
192
  old_vars = set_env_vars(self.waldiez.get_flow_env_vars())
198
193
  module = importlib.util.module_from_spec(spec)
199
194
  spec.loader.exec_module(module)
200
- printer("<Waldiez> - Starting workflow...")
201
- printer(self.waldiez.info.model_dump_json())
195
+ print("<Waldiez> - Starting workflow...")
196
+ print(self.waldiez.info.model_dump_json())
202
197
  return module, old_vars
203
198
 
204
199
  def _run(
@@ -232,8 +227,7 @@ class WaldiezRunner:
232
227
  if not self._called_install_requirements:
233
228
  self.install_requirements()
234
229
  refresh_environment()
235
- printer = get_printer()
236
- printer(
230
+ print(
237
231
  "Requirements installed.\n"
238
232
  "NOTE: If new packages were added and you are using Jupyter, "
239
233
  "you might need to restart the kernel."
@@ -246,7 +240,6 @@ class WaldiezRunner:
246
240
  temp_dir=temp_dir,
247
241
  file_name=file_name,
248
242
  module_name=module_name,
249
- printer=printer,
250
243
  )
251
244
  if use_structured_io:
252
245
  stream = StructuredIOStream(
@@ -257,13 +250,12 @@ class WaldiezRunner:
257
250
  results = module.main()
258
251
  else:
259
252
  results = module.main()
260
- printer("<Waldiez> - Workflow finished")
253
+ print("<Waldiez> - Workflow finished")
261
254
  sys.path.pop(0)
262
255
  reset_env_vars(old_vars)
263
256
  after_run(
264
257
  temp_dir=temp_dir,
265
258
  output_path=output_path,
266
- printer=printer,
267
259
  flow_name=self.waldiez.name,
268
260
  skip_mmd=skip_mmd,
269
261
  )
@@ -285,8 +277,7 @@ class WaldiezRunner:
285
277
  if not self._called_install_requirements:
286
278
  await self.a_install_requirements()
287
279
  refresh_environment()
288
- printer = get_printer()
289
- printer(
280
+ print(
290
281
  "Requirements installed.\n"
291
282
  "NOTE: If new packages were added and you are using Jupyter, "
292
283
  "you might need to restart the kernel."
@@ -299,7 +290,6 @@ class WaldiezRunner:
299
290
  temp_dir=temp_dir,
300
291
  file_name=file_name,
301
292
  module_name=module_name,
302
- printer=printer,
303
293
  )
304
294
  if use_structured_io:
305
295
  stream = StructuredIOStream(
@@ -315,7 +305,6 @@ class WaldiezRunner:
315
305
  after_run(
316
306
  temp_dir=temp_dir,
317
307
  output_path=output_path,
318
- printer=printer,
319
308
  flow_name=self.waldiez.name,
320
309
  skip_mmd=skip_mmd,
321
310
  )
@@ -9,23 +9,29 @@ from .environment import (
9
9
  reset_env_vars,
10
10
  set_env_vars,
11
11
  )
12
- from .running import (
13
- a_chdir,
12
+ from .post_run import after_run
13
+ from .pre_run import (
14
14
  a_install_requirements,
15
- after_run,
16
15
  before_run,
17
- chdir,
18
- get_printer,
19
16
  install_requirements,
20
17
  )
18
+ from .util import (
19
+ a_chdir,
20
+ chdir,
21
+ create_async_subprocess,
22
+ create_sync_subprocess,
23
+ strip_ansi,
24
+ )
21
25
 
22
26
  __all__ = [
23
27
  "a_chdir",
24
28
  "a_install_requirements",
25
29
  "after_run",
26
30
  "before_run",
31
+ "create_async_subprocess",
32
+ "create_sync_subprocess",
33
+ "strip_ansi",
27
34
  "chdir",
28
- "get_printer",
29
35
  "in_virtualenv",
30
36
  "is_root",
31
37
  "install_requirements",
@@ -0,0 +1,119 @@
1
+ # SPDX-License-Identifier: Apache-2.0.
2
+ # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+ """Utilities for running code."""
4
+
5
+ import datetime
6
+ import shutil
7
+ from pathlib import Path
8
+ from typing import Optional, Union
9
+
10
+ from .gen_seq_diagram import generate_sequence_diagram
11
+
12
+
13
+ def after_run(
14
+ temp_dir: Path,
15
+ output_path: Optional[Union[str, Path]],
16
+ flow_name: str,
17
+ skip_mmd: bool = False,
18
+ ) -> None:
19
+ """Actions to perform after running the flow.
20
+
21
+ Parameters
22
+ ----------
23
+ temp_dir : Path
24
+ The temporary directory.
25
+ output_path : Optional[Union[str, Path]]
26
+ The output path.
27
+ flow_name : str
28
+ The flow name.
29
+ skip_mmd : bool, optional
30
+ Whether to skip the mermaid sequence diagram generation,
31
+ by default, False
32
+ """
33
+ if isinstance(output_path, str):
34
+ output_path = Path(output_path)
35
+ mmd_dir = output_path.parent if output_path else Path.cwd()
36
+ if skip_mmd is False:
37
+ events_csv_path = temp_dir / "logs" / "events.csv"
38
+ if events_csv_path.exists():
39
+ print("Generating mermaid sequence diagram...")
40
+ mmd_path = temp_dir / f"{flow_name}.mmd"
41
+ generate_sequence_diagram(events_csv_path, mmd_path)
42
+ if (
43
+ not output_path
44
+ and mmd_path.exists()
45
+ and mmd_path != mmd_dir / f"{flow_name}.mmd"
46
+ ):
47
+ try:
48
+ shutil.copyfile(mmd_path, mmd_dir / f"{flow_name}.mmd")
49
+ except BaseException: # pylint: disable=broad-exception-caught
50
+ pass
51
+ if output_path:
52
+ destination_dir = output_path.parent
53
+ destination_dir = (
54
+ destination_dir
55
+ / "waldiez_out"
56
+ / datetime.datetime.now().strftime("%Y%m%d%H%M%S")
57
+ )
58
+ destination_dir.mkdir(parents=True, exist_ok=True)
59
+ # copy the contents of the temp dir to the destination dir
60
+ print(f"Copying the results to {destination_dir}")
61
+ copy_results(
62
+ temp_dir=temp_dir,
63
+ output_path=output_path,
64
+ output_dir=output_path.parent,
65
+ destination_dir=destination_dir,
66
+ )
67
+ shutil.rmtree(temp_dir)
68
+
69
+
70
+ def copy_results(
71
+ temp_dir: Path,
72
+ output_path: Path,
73
+ output_dir: Path,
74
+ destination_dir: Path,
75
+ ) -> None:
76
+ """Copy the results to the output directory.
77
+
78
+ Parameters
79
+ ----------
80
+ temp_dir : Path
81
+ The temporary directory.
82
+ output_path : Path
83
+ The output path.
84
+ output_dir : Path
85
+ The output directory.
86
+ destination_dir : Path
87
+ The destination directory.
88
+ """
89
+ temp_dir.mkdir(parents=True, exist_ok=True)
90
+ for item in temp_dir.iterdir():
91
+ # skip cache files
92
+ if (
93
+ item.name.startswith("__pycache__")
94
+ or item.name.endswith(".pyc")
95
+ or item.name.endswith(".pyo")
96
+ or item.name.endswith(".pyd")
97
+ or item.name == ".cache"
98
+ ):
99
+ continue
100
+ if item.is_file():
101
+ # let's also copy the "tree of thoughts" image
102
+ # to the output directory
103
+ if item.name.endswith("tree_of_thoughts.png") or item.name.endswith(
104
+ "reasoning_tree.json"
105
+ ):
106
+ shutil.copy(item, output_dir / item.name)
107
+ shutil.copy(item, destination_dir)
108
+ else:
109
+ shutil.copytree(item, destination_dir / item.name)
110
+ if output_path.is_file():
111
+ if output_path.suffix == ".waldiez":
112
+ output_path = output_path.with_suffix(".py")
113
+ if output_path.suffix == ".py":
114
+ src = temp_dir / output_path.name
115
+ if src.exists():
116
+ dst = destination_dir / output_path.name
117
+ if dst.exists():
118
+ dst.unlink()
119
+ shutil.copyfile(src, output_dir / output_path.name)
@@ -0,0 +1,149 @@
1
+ # SPDX-License-Identifier: Apache-2.0.
2
+ # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+ """Actions to perform before running the flow."""
4
+
5
+ import asyncio
6
+ import io
7
+ import os
8
+ import subprocess
9
+ import sys
10
+ import tempfile
11
+ from pathlib import Path
12
+ from typing import Callable, Optional, Union
13
+
14
+ from .environment import in_virtualenv, is_root
15
+ from .util import strip_ansi
16
+
17
+
18
+ def before_run(
19
+ output_path: Optional[Union[str, Path]],
20
+ uploads_root: Optional[Union[str, Path]],
21
+ ) -> str:
22
+ """Actions to perform before running the flow.
23
+
24
+ Parameters
25
+ ----------
26
+ output_path : Optional[Union[str, Path]]
27
+ The output path.
28
+ uploads_root : Optional[Union[str, Path]]
29
+ The runtime uploads root.
30
+
31
+ Returns
32
+ -------
33
+ str
34
+ The file name.
35
+ """
36
+ if not uploads_root:
37
+ uploads_root = Path(tempfile.mkdtemp())
38
+ else:
39
+ uploads_root = Path(uploads_root)
40
+ if not uploads_root.exists():
41
+ uploads_root.mkdir(parents=True)
42
+ output_dir = Path.cwd()
43
+ if output_path and isinstance(output_path, str):
44
+ output_path = Path(output_path)
45
+ if output_path:
46
+ if output_path.is_dir():
47
+ output_dir = output_path
48
+ else:
49
+ output_dir = output_path.parent if output_path else Path.cwd()
50
+ if not output_dir.exists():
51
+ output_dir.mkdir(parents=True, exist_ok=True)
52
+ file_name = Path(output_path).name if output_path else "waldiez_flow.py"
53
+ if file_name.endswith((".json", ".waldiez")):
54
+ file_name = file_name.replace(".json", ".py").replace(".waldiez", ".py")
55
+ if not file_name.endswith(".py"):
56
+ file_name += ".py"
57
+ return file_name
58
+
59
+
60
+ def install_requirements(
61
+ extra_requirements: set[str],
62
+ printer: Callable[..., None] = print,
63
+ ) -> None:
64
+ """Install the requirements.
65
+
66
+ Parameters
67
+ ----------
68
+ extra_requirements : set[str]
69
+ The extra requirements.
70
+ printer : Callable[..., None]
71
+ The printer function to use, defaults to print.
72
+ """
73
+ requirements_string = ", ".join(extra_requirements)
74
+ printer(f"Installing requirements: {requirements_string}")
75
+ pip_install = [sys.executable, "-m", "pip", "install"]
76
+ break_system_packages = ""
77
+ if not in_virtualenv(): # it should
78
+ # if not, let's try to install as user
79
+ # not sure if --break-system-packages is safe,
80
+ # but it might fail if we don't
81
+ break_system_packages = os.environ.get("PIP_BREAK_SYSTEM_PACKAGES", "")
82
+ os.environ["PIP_BREAK_SYSTEM_PACKAGES"] = "1"
83
+ if not is_root():
84
+ pip_install.append("--user")
85
+ pip_install.extend(extra_requirements)
86
+ # pylint: disable=too-many-try-statements
87
+ try:
88
+ with subprocess.Popen(
89
+ pip_install,
90
+ stdout=subprocess.PIPE,
91
+ stderr=subprocess.PIPE,
92
+ ) as proc:
93
+ if proc.stdout:
94
+ for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):
95
+ printer(strip_ansi(line.strip()))
96
+ if proc.stderr:
97
+ for line in io.TextIOWrapper(proc.stderr, encoding="utf-8"):
98
+ printer(strip_ansi(line.strip()))
99
+ finally:
100
+ if not in_virtualenv():
101
+ # restore the old env var
102
+ if break_system_packages:
103
+ os.environ["PIP_BREAK_SYSTEM_PACKAGES"] = break_system_packages
104
+ else:
105
+ del os.environ["PIP_BREAK_SYSTEM_PACKAGES"]
106
+
107
+
108
+ async def a_install_requirements(
109
+ extra_requirements: set[str],
110
+ printer: Callable[..., None] = print,
111
+ ) -> None:
112
+ """Install the requirements asynchronously.
113
+
114
+ Parameters
115
+ ----------
116
+ extra_requirements : set[str]
117
+ The extra requirements.
118
+ printer : Callable[..., None]
119
+ The printer function to use, defaults to print.
120
+ """
121
+ requirements_string = ", ".join(extra_requirements)
122
+ printer(f"Installing requirements: {requirements_string}")
123
+ pip_install = [sys.executable, "-m", "pip", "install"]
124
+ break_system_packages = ""
125
+ if not in_virtualenv():
126
+ break_system_packages = os.environ.get("PIP_BREAK_SYSTEM_PACKAGES", "")
127
+ os.environ["PIP_BREAK_SYSTEM_PACKAGES"] = "1"
128
+ if not is_root():
129
+ pip_install.extend(["--user"])
130
+ pip_install.extend(extra_requirements)
131
+ # pylint: disable=too-many-try-statements
132
+ try:
133
+ proc = await asyncio.create_subprocess_exec(
134
+ *pip_install,
135
+ stdout=asyncio.subprocess.PIPE,
136
+ stderr=asyncio.subprocess.PIPE,
137
+ )
138
+ if proc.stdout:
139
+ async for line in proc.stdout:
140
+ printer(strip_ansi(line.decode().strip()))
141
+ if proc.stderr:
142
+ async for line in proc.stderr:
143
+ printer(strip_ansi(line.decode().strip()))
144
+ finally:
145
+ if not in_virtualenv():
146
+ if break_system_packages:
147
+ os.environ["PIP_BREAK_SYSTEM_PACKAGES"] = break_system_packages
148
+ else:
149
+ del os.environ["PIP_BREAK_SYSTEM_PACKAGES"]
@@ -0,0 +1,134 @@
1
+ # SPDX-License-Identifier: Apache-2.0.
2
+ # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+ """Common utilities for the waldiez runner."""
4
+
5
+ import asyncio
6
+ import os
7
+ import re
8
+ import subprocess
9
+ import sys
10
+ from asyncio.subprocess import Process
11
+ from contextlib import asynccontextmanager, contextmanager
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+ from typing import AsyncIterator, Iterator, Union
15
+
16
+
17
+ @dataclass
18
+ class ProcessSetup:
19
+ """Container for subprocess setup data."""
20
+
21
+ temp_dir: Path
22
+ file_path: Path
23
+ old_vars: dict[str, str]
24
+ skip_mmd: bool
25
+
26
+
27
+ @contextmanager
28
+ def chdir(to: Union[str, Path]) -> Iterator[None]:
29
+ """Change the current working directory in a context.
30
+
31
+ Parameters
32
+ ----------
33
+ to : Union[str, Path]
34
+ The directory to change to.
35
+
36
+ Yields
37
+ ------
38
+ Iterator[None]
39
+ The context manager.
40
+ """
41
+ old_cwd = str(os.getcwd())
42
+ os.chdir(to)
43
+ try:
44
+ yield
45
+ finally:
46
+ os.chdir(old_cwd)
47
+
48
+
49
+ @asynccontextmanager
50
+ async def a_chdir(to: Union[str, Path]) -> AsyncIterator[None]:
51
+ """Asynchronously change the current working directory in a context.
52
+
53
+ Parameters
54
+ ----------
55
+ to : Union[str, Path]
56
+ The directory to change to.
57
+
58
+ Yields
59
+ ------
60
+ AsyncIterator[None]
61
+ The async context manager.
62
+ """
63
+ old_cwd = str(os.getcwd())
64
+ os.chdir(to)
65
+ try:
66
+ yield
67
+ finally:
68
+ os.chdir(old_cwd)
69
+
70
+
71
+ def strip_ansi(text: str) -> str:
72
+ """Remove ANSI escape sequences from text.
73
+
74
+ Parameters
75
+ ----------
76
+ text : str
77
+ The text to strip.
78
+
79
+ Returns
80
+ -------
81
+ str
82
+ The text without ANSI escape sequences.
83
+ """
84
+ ansi_pattern = re.compile(r"\x1b\[[0-9;]*m|\x1b\[.*?[@-~]")
85
+ return ansi_pattern.sub("", text)
86
+
87
+
88
+ def create_sync_subprocess(setup: ProcessSetup) -> subprocess.Popen[bytes]:
89
+ """Create a synchronous subprocess.
90
+
91
+ Parameters
92
+ ----------
93
+ setup : ProcessSetup
94
+ The setup data for the subprocess.
95
+
96
+ Returns
97
+ -------
98
+ subprocess.Popen[bytes]
99
+ The created subprocess.
100
+ """
101
+ return subprocess.Popen(
102
+ [sys.executable, "-u", str(setup.file_path)],
103
+ stdout=subprocess.PIPE,
104
+ stderr=subprocess.PIPE,
105
+ stdin=subprocess.PIPE,
106
+ # text=True,
107
+ # bufsize=1, # Line buffered for real-time output
108
+ # universal_newlines=True,
109
+ env={**os.environ},
110
+ )
111
+
112
+
113
+ async def create_async_subprocess(setup: ProcessSetup) -> Process:
114
+ """Create an asynchronous subprocess.
115
+
116
+ Parameters
117
+ ----------
118
+ setup : ProcessSetup
119
+ The setup data for the subprocess.
120
+
121
+ Returns
122
+ -------
123
+ Process
124
+ The created asynchronous subprocess.
125
+ """
126
+ return await asyncio.create_subprocess_exec(
127
+ sys.executable,
128
+ "-u",
129
+ str(setup.file_path),
130
+ # stdout=asyncio.subprocess.PIPE,
131
+ # stderr=asyncio.subprocess.PIPE,
132
+ # stdin=asyncio.subprocess.PIPE,
133
+ env={**os.environ},
134
+ )
waldiez/utils/__init__.py CHANGED
@@ -5,9 +5,11 @@
5
5
  from .cli_extras import add_cli_extras
6
6
  from .conflict_checker import check_conflicts
7
7
  from .flaml_warnings import check_flaml_warnings
8
+ from .version import get_waldiez_version
8
9
 
9
10
  __all__ = [
10
11
  "check_conflicts",
11
12
  "check_flaml_warnings",
12
13
  "add_cli_extras",
14
+ "get_waldiez_version",
13
15
  ]
@@ -0,0 +1,47 @@
1
+ # SPDX-License-Identifier: Apache-2.0.
2
+ # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+ """Try to get the Waldiez version."""
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+
9
+ def _get_waldiez_version_from_package_json() -> str | None:
10
+ """Get the Waldiez version from package.json."""
11
+ package_json_path = Path(__file__).parent.parent.parent / "package.json"
12
+ if package_json_path.exists():
13
+ with open(package_json_path, "r", encoding="utf-8") as f:
14
+ package_data = json.load(f)
15
+ return package_data.get("version", None)
16
+ return None
17
+
18
+
19
+ def _get_waldiez_version_from_version_py() -> str | None:
20
+ """Get the Waldiez version from _version.py."""
21
+ version_py_path = Path(__file__).parent.parent / "_version.py"
22
+ if version_py_path.exists():
23
+ with open(version_py_path, "r", encoding="utf-8") as f:
24
+ for line in f:
25
+ if line.startswith("__version__"):
26
+ return line.split('"')[1]
27
+ return None
28
+
29
+
30
+ def get_waldiez_version() -> str:
31
+ """Get the Waldiez version.
32
+
33
+ Returns
34
+ -------
35
+ str
36
+ The Waldiez version, or "dev" if not found.
37
+ """
38
+ w_version = _get_waldiez_version_from_package_json()
39
+ if not w_version:
40
+ w_version = _get_waldiez_version_from_version_py()
41
+ if not w_version:
42
+ w_version = "dev"
43
+ return w_version
44
+
45
+
46
+ if __name__ == "__main__":
47
+ print(get_waldiez_version())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: waldiez
3
- Version: 0.4.8
3
+ Version: 0.4.9
4
4
  Dynamic: Keywords
5
5
  Summary: Make AG2 Agents Collaborate: Drag, Drop, and Orchestrate with Waldiez.
6
6
  Project-URL: Homepage, https://waldiez.io
@@ -157,15 +157,15 @@ Requires-Dist: natsort==8.4.0; extra == 'docs'
157
157
  Provides-Extra: jupyter
158
158
  Requires-Dist: jupyter-server==2.16.0; extra == 'jupyter'
159
159
  Requires-Dist: jupyterlab<5.0,>=4.3.0; extra == 'jupyter'
160
- Requires-Dist: waldiez-jupyter==0.4.8; extra == 'jupyter'
160
+ Requires-Dist: waldiez-jupyter==0.4.9; extra == 'jupyter'
161
161
  Provides-Extra: mqtt
162
162
  Requires-Dist: paho-mqtt<3.0,>=2.1.0; extra == 'mqtt'
163
163
  Provides-Extra: redis
164
164
  Requires-Dist: ag2[redis]==0.9.2; extra == 'redis'
165
165
  Provides-Extra: runner
166
- Requires-Dist: waldiez-runner==0.4.8; (python_version >= '3.11') and extra == 'runner'
166
+ Requires-Dist: waldiez-runner==0.4.9; (python_version >= '3.11') and extra == 'runner'
167
167
  Provides-Extra: studio
168
- Requires-Dist: waldiez-studio==0.4.8; extra == 'studio'
168
+ Requires-Dist: waldiez-studio==0.4.9; extra == 'studio'
169
169
  Provides-Extra: test
170
170
  Requires-Dist: ag2[redis]==0.9.2; extra == 'test'
171
171
  Requires-Dist: ag2[websockets]==0.9.2; extra == 'test'
@@ -1,11 +1,11 @@
1
1
  waldiez/__init__.py,sha256=JVVh6d5Zw3ch4GYtIwXZEBpMEyJZAc5t0mtu08tLbZs,992
2
2
  waldiez/__main__.py,sha256=0dYzNrQbovRwQQvmZC6_1FDR1m71SUIOkTleO5tBnBw,203
3
- waldiez/_version.py,sha256=Nlo6ovErTlkRrYOk191gkWS6guip7lym_CNGuVCS1nI,249
4
- waldiez/cli.py,sha256=LAxEdPMBP7Un0hUGUmW7U9KaSgzJxtjQejS9CZ8LPKk,8804
3
+ waldiez/_version.py,sha256=De8aF7saKAU1lagF5W1Ha6s0ndjgEGVM1xVTicmFN_8,249
4
+ waldiez/cli.py,sha256=FuWHXi1NWNhuXyUHVEvSnS0450Hh0LPekfsZurML2us,7427
5
5
  waldiez/exporter.py,sha256=Gl23KC-pF2986gnHLqGf1-7RAPfvbf2sz4VX236gXO8,6134
6
6
  waldiez/logger.py,sha256=UFdPwS2AOl2LkFQVonDSET9Dguq5jsn2mV817iTZFJc,16375
7
7
  waldiez/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- waldiez/runner.py,sha256=mzMsk-YKhgsKpIPW5DW1nUv-wXo_P56k8QkKpntlw5A,13875
8
+ waldiez/runner.py,sha256=E-e5_QPFYWvWgcY7FwOz5lso2toV1enDD2pnP_Ej1fE,13526
9
9
  waldiez/exporting/__init__.py,sha256=xVOqb3gADkrTmp_BVrQ-dMPiySPUDU4xZmqYqhzL8BI,1026
10
10
  waldiez/exporting/agent/__init__.py,sha256=GpNfVQLAvGoJP_U4WkMMIGpEB8k7vEV-FVR446s2zhY,260
11
11
  waldiez/exporting/agent/code_execution.py,sha256=sxjulr2OmeSg0SrYz7rgpBgF_AfqpHfV86kmtGU49Yk,3795
@@ -28,7 +28,7 @@ waldiez/exporting/agent/extras/handoffs/condition.py,sha256=kURtSLS5B0WbXPARff3t
28
28
  waldiez/exporting/agent/extras/handoffs/handoff.py,sha256=Xp0sYpXIoXQ6GbLrLPxylDpyNPiE2w1oXmQ-UqfnK7U,5918
29
29
  waldiez/exporting/agent/extras/handoffs/target.py,sha256=R7KerfkL1kXkGMnoy1GXaOTH6DM6LSKXl7ohpTcInCE,6527
30
30
  waldiez/exporting/agent/extras/rag/__init__.py,sha256=jkKSaHLfwA8Tj7QJH0MSdAphaP4HowuhQXDj7V122Ek,259
31
- waldiez/exporting/agent/extras/rag/chroma_extras.py,sha256=m6PMx4opQ9VJPHdEW-kOmOZeqibLBeVDkkLxiZFkUTY,5018
31
+ waldiez/exporting/agent/extras/rag/chroma_extras.py,sha256=vf4EuikCZPtqFEOCoB01YcD85Hi_SA37b1JO869DqSA,5555
32
32
  waldiez/exporting/agent/extras/rag/mongo_extras.py,sha256=bn_5bfmyfdW5eQCOjcCRYPKLIJ7vZXKEiUsa4zPx7Sk,2822
33
33
  waldiez/exporting/agent/extras/rag/pgvector_extras.py,sha256=Y08rU8Z3-oHUbh7huO1Jo_PZLlOW-NRa1Gz3-d9WXso,2961
34
34
  waldiez/exporting/agent/extras/rag/qdrant_extras.py,sha256=hTzY7GUL8RBMFNEGefLDAxKFMSlD5Qmkzm3Ducgp9vU,3633
@@ -97,7 +97,7 @@ waldiez/io/__init__.py,sha256=x4-LxBgHd0ekLXyccgcgV8ERwKRENaJQxaegULkRW3g,3361
97
97
  waldiez/io/_ws.py,sha256=uyuIfCrBK7LDXowTQ2fsbP4jvjUcIQZqPQefx2UXmp8,5652
98
98
  waldiez/io/mqtt.py,sha256=T01XCGyoWYdIEhzxS5M9igbYLgZqhF-K63j3JQDmj5E,22418
99
99
  waldiez/io/redis.py,sha256=diz0UqgcpIejBGFb9TOoT2iVzGtHWFnhg0cfEOox25Q,25523
100
- waldiez/io/structured.py,sha256=n-YeW-BFgNzFJU7QXbG3rG6v6R58ZltttouYZm2rZ6E,13717
100
+ waldiez/io/structured.py,sha256=ZFmiaX-OOIX86SDMg3h0Gw1OwmrNgi7PgIKu9V1gUrk,14409
101
101
  waldiez/io/utils.py,sha256=J2I0cuBMMjJadM8OjP7kiqFjoZp8hc9rXF9n5fdltEE,4488
102
102
  waldiez/io/ws.py,sha256=aiORAYjvSA57ZqI1mXINkkHnZzqkibaICTj2bZ9Yze8,9107
103
103
  waldiez/io/models/__init__.py,sha256=N21ET1_QEkCsJNrax8y5cDDiTrGj2rv9BfEsVvUY1m0,1240
@@ -181,20 +181,23 @@ waldiez/models/tool/extra_requirements.py,sha256=kNrmWxS96FhO93NV3B5Hr5wAJrjuUWb
181
181
  waldiez/models/tool/tool.py,sha256=Mzst4x9tS-1exryTYZSAlr6B3E7ba7Q5yF9WLxXp5G8,8987
182
182
  waldiez/models/tool/tool_data.py,sha256=lOtIetRM35tUvDCtjiHjddVSoqlwV4NFkz_496GLunA,1305
183
183
  waldiez/models/tool/tool_type.py,sha256=VYcgnAlj1n6ZqXA8D0VdbRfZ-_U89CPSDuNgk822_UA,268
184
- waldiez/running/__init__.py,sha256=EqH4knSyNxYjN31YBifoLfXS5nEW4ItWMA6-CNpZszk,662
184
+ waldiez/running/__init__.py,sha256=Q_lhpEdJ2HoXOAISQYdCesUYMctGqq59AdYsa3Gr1yI,817
185
185
  waldiez/running/environment.py,sha256=DxiI9GqFrFiF6p5icrpCFkbsvq6zgACoqXYyHZniffQ,3472
186
186
  waldiez/running/gen_seq_diagram.py,sha256=wsF9b9uG0NA5iVAlW-wOtYn0w_9-bySBO9nWkUIBNUc,5943
187
- waldiez/running/running.py,sha256=0nZ33hwvUAVGcemT2C2Rec8fqHkB5xU4TRiCzs1fAKE,11750
188
- waldiez/utils/__init__.py,sha256=ITQcsqe2jhO376VSeZGfcf0n0kUhMw-4rKxUWtFB8SY,345
187
+ waldiez/running/post_run.py,sha256=rxVmRndpKnoJs2uZrk0JDNrrFNihoU52rExyRxgJe0k,3891
188
+ waldiez/running/pre_run.py,sha256=lM5l6lLLgqlKpWS3o2Gbe_VhDvVJ6xgJZsbL8rYKJpY,5019
189
+ waldiez/running/util.py,sha256=7DnuL-FNkraDhu3QUV3GEZC_dNgo3IMlptBzG9tMUKA,3002
190
+ waldiez/utils/__init__.py,sha256=lamPdfOEeOZ9iYtDXKXEyOL2RhjZ7wpyK2W5SmVxPNc,413
189
191
  waldiez/utils/conflict_checker.py,sha256=bskxC2KmPrAOjjYfgQhGXgNxdU5Z10ZZmx-_c0W8I7I,1493
190
192
  waldiez/utils/flaml_warnings.py,sha256=gN1yzlAAHor04w0-_xXUK0wID6aXCizKYElaNHflkg8,630
193
+ waldiez/utils/version.py,sha256=pYu3CwBW5Rwv9LqWG8W5DxrvdIU1nc2MOY1RDJR8oc4,1417
191
194
  waldiez/utils/cli_extras/__init__.py,sha256=ZvuLaN3IGuffWMh7mladTGkJxx3kn5IepUF3jk4ZAWY,684
192
195
  waldiez/utils/cli_extras/jupyter.py,sha256=nOQraO7Xg1G8SCP93OpIVheXieXW5ireSC9Oir-dXzA,3047
193
196
  waldiez/utils/cli_extras/runner.py,sha256=V_aoTKZsvtPgjszvWlPamKjnOG93L2aY6sBcHnIh9XI,983
194
197
  waldiez/utils/cli_extras/studio.py,sha256=p4CMDCYW6lxrPvHvWX2kpsE1Ix7NnKZg1Jf15weWirE,984
195
- waldiez-0.4.8.dist-info/METADATA,sha256=3Ta0RN91r4JKHXSf8hZ7P4KekETUeJeLgquleK0pS6w,22620
196
- waldiez-0.4.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
197
- waldiez-0.4.8.dist-info/entry_points.txt,sha256=9MQ8Y1rD19CU7UwjNPwoyTRpQsPs2QimjrtwTD0bD6k,44
198
- waldiez-0.4.8.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
199
- waldiez-0.4.8.dist-info/licenses/NOTICE.md,sha256=L7xtckFRYvYJjhjQNtFpURWCiAvEuq4ePvxJsC-XAdk,785
200
- waldiez-0.4.8.dist-info/RECORD,,
198
+ waldiez-0.4.9.dist-info/METADATA,sha256=FefkfD_VJwlzOf2tuWvcPpjYvBsQ8AYlw9sOgPmzh9Y,22620
199
+ waldiez-0.4.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
200
+ waldiez-0.4.9.dist-info/entry_points.txt,sha256=9MQ8Y1rD19CU7UwjNPwoyTRpQsPs2QimjrtwTD0bD6k,44
201
+ waldiez-0.4.9.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
202
+ waldiez-0.4.9.dist-info/licenses/NOTICE.md,sha256=L7xtckFRYvYJjhjQNtFpURWCiAvEuq4ePvxJsC-XAdk,785
203
+ waldiez-0.4.9.dist-info/RECORD,,
@@ -1,388 +0,0 @@
1
- # SPDX-License-Identifier: Apache-2.0.
2
- # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
- """Utilities for running code."""
4
-
5
- import asyncio
6
- import datetime
7
- import io
8
- import os
9
- import re
10
- import shutil
11
- import subprocess
12
- import sys
13
- import tempfile
14
- from contextlib import asynccontextmanager, contextmanager
15
- from pathlib import Path
16
- from typing import (
17
- Any,
18
- AsyncIterator,
19
- Callable,
20
- Iterator,
21
- Optional,
22
- Set,
23
- Union,
24
- )
25
-
26
- from .environment import in_virtualenv, is_root
27
- from .gen_seq_diagram import generate_sequence_diagram
28
-
29
- # pylint: disable=import-outside-toplevel
30
-
31
-
32
- @contextmanager
33
- def chdir(to: Union[str, Path]) -> Iterator[None]:
34
- """Change the current working directory in a context.
35
-
36
- Parameters
37
- ----------
38
- to : Union[str, Path]
39
- The directory to change to.
40
-
41
- Yields
42
- ------
43
- Iterator[None]
44
- The context manager.
45
- """
46
- old_cwd = str(os.getcwd())
47
- os.chdir(to)
48
- try:
49
- yield
50
- finally:
51
- os.chdir(old_cwd)
52
-
53
-
54
- @asynccontextmanager
55
- async def a_chdir(to: Union[str, Path]) -> AsyncIterator[None]:
56
- """Asynchronously change the current working directory in a context.
57
-
58
- Parameters
59
- ----------
60
- to : Union[str, Path]
61
- The directory to change to.
62
-
63
- Yields
64
- ------
65
- AsyncIterator[None]
66
- The async context manager.
67
- """
68
- old_cwd = str(os.getcwd())
69
- os.chdir(to)
70
- try:
71
- yield
72
- finally:
73
- os.chdir(old_cwd)
74
-
75
-
76
- def strip_ansi(text: str) -> str:
77
- """Remove ANSI escape sequences from text.
78
-
79
- Parameters
80
- ----------
81
- text : str
82
- The text to strip.
83
-
84
- Returns
85
- -------
86
- str
87
- The text without ANSI escape sequences.
88
- """
89
- ansi_pattern = re.compile(r"\x1b\[[0-9;]*m|\x1b\[.*?[@-~]")
90
- return ansi_pattern.sub("", text)
91
-
92
-
93
- def before_run(
94
- output_path: Optional[Union[str, Path]],
95
- uploads_root: Optional[Union[str, Path]],
96
- ) -> str:
97
- """Actions to perform before running the flow.
98
-
99
- Parameters
100
- ----------
101
- output_path : Optional[Union[str, Path]]
102
- The output path.
103
- uploads_root : Optional[Union[str, Path]]
104
- The runtime uploads root.
105
-
106
- Returns
107
- -------
108
- str
109
- The file name.
110
- """
111
- if not uploads_root:
112
- uploads_root = Path(tempfile.mkdtemp())
113
- else:
114
- uploads_root = Path(uploads_root)
115
- if not uploads_root.exists():
116
- uploads_root.mkdir(parents=True)
117
- file_name = "waldiez_flow.py" if not output_path else Path(output_path).name
118
- if file_name.endswith((".json", ".waldiez")):
119
- file_name = file_name.replace(".json", ".py").replace(".waldiez", ".py")
120
- if not file_name.endswith(".py"):
121
- file_name += ".py"
122
- return file_name
123
-
124
-
125
- def install_requirements(
126
- extra_requirements: Set[str], printer: Callable[..., None]
127
- ) -> None:
128
- """Install the requirements.
129
-
130
- Parameters
131
- ----------
132
- extra_requirements : Set[str]
133
- The extra requirements.
134
- printer : Callable[..., None]
135
- The printer function.
136
- """
137
- requirements_string = ", ".join(extra_requirements)
138
- printer(f"Installing requirements: {requirements_string}")
139
- pip_install = [sys.executable, "-m", "pip", "install"]
140
- break_system_packages = ""
141
- if not in_virtualenv(): # it should
142
- # if not, let's try to install as user
143
- # not sure if --break-system-packages is safe,
144
- # but it might fail if we don't
145
- break_system_packages = os.environ.get("PIP_BREAK_SYSTEM_PACKAGES", "")
146
- os.environ["PIP_BREAK_SYSTEM_PACKAGES"] = "1"
147
- if not is_root():
148
- pip_install.append("--user")
149
- pip_install.extend(extra_requirements)
150
- # pylint: disable=too-many-try-statements
151
- try:
152
- with subprocess.Popen(
153
- pip_install,
154
- stdout=subprocess.PIPE,
155
- stderr=subprocess.PIPE,
156
- ) as proc:
157
- if proc.stdout:
158
- for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):
159
- printer(strip_ansi(line.strip()))
160
- if proc.stderr:
161
- for line in io.TextIOWrapper(proc.stderr, encoding="utf-8"):
162
- printer(strip_ansi(line.strip()))
163
- finally:
164
- if not in_virtualenv():
165
- # restore the old env var
166
- if break_system_packages:
167
- os.environ["PIP_BREAK_SYSTEM_PACKAGES"] = break_system_packages
168
- else:
169
- del os.environ["PIP_BREAK_SYSTEM_PACKAGES"]
170
-
171
-
172
- async def a_install_requirements(
173
- extra_requirements: Set[str], printer: Callable[..., None]
174
- ) -> None:
175
- """Install the requirements asynchronously.
176
-
177
- Parameters
178
- ----------
179
- extra_requirements : Set[str]
180
- The extra requirements.
181
- printer : Callable[..., None]
182
- The printer function.
183
- """
184
- requirements_string = ", ".join(extra_requirements)
185
- printer(f"Installing requirements: {requirements_string}")
186
- pip_install = [sys.executable, "-m", "pip", "install"]
187
- break_system_packages = ""
188
- if not in_virtualenv():
189
- break_system_packages = os.environ.get("PIP_BREAK_SYSTEM_PACKAGES", "")
190
- os.environ["PIP_BREAK_SYSTEM_PACKAGES"] = "1"
191
- if not is_root():
192
- pip_install.extend(["--user"])
193
- pip_install.extend(extra_requirements)
194
- # pylint: disable=too-many-try-statements
195
- try:
196
- proc = await asyncio.create_subprocess_exec(
197
- *pip_install,
198
- stdout=asyncio.subprocess.PIPE,
199
- stderr=asyncio.subprocess.PIPE,
200
- )
201
- if proc.stdout:
202
- async for line in proc.stdout:
203
- printer(strip_ansi(line.decode().strip()))
204
- if proc.stderr:
205
- async for line in proc.stderr:
206
- printer(strip_ansi(line.decode().strip()))
207
- finally:
208
- if not in_virtualenv():
209
- if break_system_packages:
210
- os.environ["PIP_BREAK_SYSTEM_PACKAGES"] = break_system_packages
211
- else:
212
- del os.environ["PIP_BREAK_SYSTEM_PACKAGES"]
213
-
214
-
215
- def after_run(
216
- temp_dir: Path,
217
- output_path: Optional[Union[str, Path]],
218
- printer: Callable[..., None],
219
- flow_name: str,
220
- skip_mmd: bool = False,
221
- ) -> None:
222
- """Actions to perform after running the flow.
223
-
224
- Parameters
225
- ----------
226
- temp_dir : Path
227
- The temporary directory.
228
- output_path : Optional[Union[str, Path]]
229
- The output path.
230
- printer : Callable[..., None]
231
- The printer function.
232
- flow_name : str
233
- The flow name.
234
- skip_mmd : bool, optional
235
- Whether to skip the mermaid sequence diagram generation,
236
- by default, False
237
- """
238
- if isinstance(output_path, str):
239
- output_path = Path(output_path)
240
- output_dir = output_path.parent if output_path else Path.cwd()
241
- if skip_mmd is False:
242
- events_csv_path = temp_dir / "logs" / "events.csv"
243
- if events_csv_path.exists():
244
- printer("Generating mermaid sequence diagram...")
245
- mmd_path = temp_dir / f"{flow_name}.mmd"
246
- generate_sequence_diagram(events_csv_path, mmd_path)
247
- if mmd_path.exists():
248
- shutil.copyfile(mmd_path, output_dir / f"{flow_name}.mmd")
249
- if output_path:
250
- destination_dir = output_path.parent
251
- destination_dir = (
252
- destination_dir
253
- / "waldiez_out"
254
- / datetime.datetime.now().strftime("%Y%m%d%H%M%S")
255
- )
256
- destination_dir.mkdir(parents=True, exist_ok=True)
257
- # copy the contents of the temp dir to the destination dir
258
- printer(f"Copying the results to {destination_dir}")
259
- copy_results(
260
- temp_dir=temp_dir,
261
- output_path=output_path,
262
- output_dir=output_dir,
263
- destination_dir=destination_dir,
264
- )
265
- shutil.rmtree(temp_dir)
266
-
267
-
268
- def copy_results(
269
- temp_dir: Path,
270
- output_path: Path,
271
- output_dir: Path,
272
- destination_dir: Path,
273
- ) -> None:
274
- """Copy the results to the output directory.
275
-
276
- Parameters
277
- ----------
278
- temp_dir : Path
279
- The temporary directory.
280
- output_path : Path
281
- The output path.
282
- output_dir : Path
283
- The output directory.
284
- destination_dir : Path
285
- The destination directory.
286
- """
287
- temp_dir.mkdir(parents=True, exist_ok=True)
288
- for item in temp_dir.iterdir():
289
- # skip cache files
290
- if (
291
- item.name.startswith("__pycache__")
292
- or item.name.endswith(".pyc")
293
- or item.name.endswith(".pyo")
294
- or item.name.endswith(".pyd")
295
- or item.name == ".cache"
296
- ):
297
- continue
298
- if item.is_file():
299
- # let's also copy the "tree of thoughts" image
300
- # to the output directory
301
- if item.name.endswith("tree_of_thoughts.png") or item.name.endswith(
302
- "reasoning_tree.json"
303
- ):
304
- shutil.copy(item, output_dir / item.name)
305
- shutil.copy(item, destination_dir)
306
- else:
307
- shutil.copytree(item, destination_dir / item.name)
308
- if output_path.is_file():
309
- if output_path.suffix == ".waldiez":
310
- output_path = output_path.with_suffix(".py")
311
- if output_path.suffix == ".py":
312
- src = temp_dir / output_path.name
313
- if src.exists():
314
- dst = destination_dir / output_path.name
315
- if dst.exists():
316
- dst.unlink()
317
- shutil.copyfile(src, output_dir / output_path.name)
318
-
319
-
320
- def get_printer() -> Callable[..., None]:
321
- """Get the printer function.
322
-
323
- Returns
324
- -------
325
- Callable[..., None]
326
- The printer function.
327
- """
328
- from autogen.io import IOStream # type: ignore
329
-
330
- # noinspection PyUnboundLocalVariable
331
- printer = IOStream.get_default().print
332
-
333
- def safe_printer(*args: Any, **kwargs: Any) -> None:
334
- try:
335
- printer(*args, **kwargs)
336
- except UnicodeEncodeError:
337
- try:
338
- # Use the helper to get safely encoded message
339
- safe_msg, flush = get_what_to_print(*args, **kwargs)
340
- # Try printing the safe message
341
- # (without end since it's already included)
342
- printer(safe_msg, end="", flush=flush)
343
- except UnicodeEncodeError:
344
- # pylint: disable=too-many-try-statements
345
- try:
346
- # If that still fails, write directly to buffer
347
- msg, flush = get_what_to_print(*args, **kwargs)
348
- safe_msg_bytes = msg.encode("utf-8", errors="replace")
349
- sys.stdout.buffer.write(safe_msg_bytes)
350
- if flush:
351
- sys.stdout.buffer.flush()
352
- except Exception: # pylint: disable=broad-exception-caught
353
- error_msg = (
354
- b"Could not print message due to encoding issues.\n"
355
- )
356
- sys.stderr.buffer.write(error_msg)
357
- sys.stderr.buffer.flush()
358
-
359
- return safe_printer
360
-
361
-
362
- def get_what_to_print(*args: object, **kwargs: object) -> tuple[str, bool]:
363
- """Get what to print.
364
-
365
- Parameters
366
- ----------
367
- args : object
368
- The print arguments.
369
- kwargs : object
370
- The keyword arguments.
371
-
372
- Returns
373
- -------
374
- tuple[str, bool]
375
- The message and whether to flush.
376
- """
377
- sep = kwargs.get("sep", " ")
378
- if not isinstance(sep, str):
379
- sep = " "
380
- end = kwargs.get("end", "\n")
381
- if not isinstance(end, str):
382
- end = "\n"
383
- flush = kwargs.get("flush", False)
384
- if not isinstance(flush, bool):
385
- flush = False
386
- msg = sep.join(str(arg) for arg in args) + end
387
- utf8_msg = msg.encode("utf-8", errors="replace").decode("utf-8")
388
- return utf8_msg, flush