waldiez 0.5.10__py3-none-any.whl → 0.6.1__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 (192) hide show
  1. waldiez/__init__.py +1 -1
  2. waldiez/_version.py +1 -1
  3. waldiez/cli.py +19 -7
  4. waldiez/cli_extras/jupyter.py +3 -0
  5. waldiez/cli_extras/runner.py +3 -1
  6. waldiez/cli_extras/studio.py +3 -1
  7. waldiez/exporter.py +9 -3
  8. waldiez/exporting/agent/exporter.py +15 -16
  9. waldiez/exporting/agent/extras/captain_agent_extras.py +6 -6
  10. waldiez/exporting/agent/extras/doc_agent_extras.py +6 -6
  11. waldiez/exporting/agent/extras/group_manager_agent_extas.py +40 -24
  12. waldiez/exporting/agent/extras/group_member_extras.py +6 -5
  13. waldiez/exporting/agent/extras/handoffs/after_work.py +2 -1
  14. waldiez/exporting/agent/extras/handoffs/available.py +2 -1
  15. waldiez/exporting/agent/extras/handoffs/condition.py +3 -2
  16. waldiez/exporting/agent/extras/handoffs/handoff.py +2 -1
  17. waldiez/exporting/agent/extras/handoffs/target.py +7 -4
  18. waldiez/exporting/agent/extras/rag/chroma_extras.py +27 -19
  19. waldiez/exporting/agent/extras/rag/mongo_extras.py +8 -8
  20. waldiez/exporting/agent/extras/rag/pgvector_extras.py +5 -5
  21. waldiez/exporting/agent/extras/rag/qdrant_extras.py +5 -4
  22. waldiez/exporting/agent/extras/rag/vector_db_extras.py +1 -1
  23. waldiez/exporting/agent/extras/rag_user_proxy_agent_extras.py +5 -7
  24. waldiez/exporting/agent/extras/reasoning_agent_extras.py +3 -5
  25. waldiez/exporting/agent/termination.py +1 -0
  26. waldiez/exporting/chats/exporter.py +4 -4
  27. waldiez/exporting/chats/processor.py +1 -2
  28. waldiez/exporting/chats/utils/common.py +89 -48
  29. waldiez/exporting/chats/utils/group.py +9 -9
  30. waldiez/exporting/chats/utils/nested.py +7 -7
  31. waldiez/exporting/chats/utils/sequential.py +1 -1
  32. waldiez/exporting/chats/utils/single.py +2 -2
  33. waldiez/exporting/core/constants.py +3 -1
  34. waldiez/exporting/core/content.py +7 -7
  35. waldiez/exporting/core/context.py +5 -3
  36. waldiez/exporting/core/exporter.py +5 -3
  37. waldiez/exporting/core/exporters.py +2 -2
  38. waldiez/exporting/core/extras/agent_extras/captain_extras.py +2 -2
  39. waldiez/exporting/core/extras/agent_extras/group_manager_extras.py +2 -2
  40. waldiez/exporting/core/extras/agent_extras/rag_user_extras.py +2 -2
  41. waldiez/exporting/core/extras/agent_extras/standard_extras.py +3 -8
  42. waldiez/exporting/core/extras/base.py +7 -5
  43. waldiez/exporting/core/extras/flow_extras.py +4 -5
  44. waldiez/exporting/core/extras/model_extras.py +2 -2
  45. waldiez/exporting/core/extras/path_resolver.py +1 -2
  46. waldiez/exporting/core/extras/serializer.py +13 -11
  47. waldiez/exporting/core/protocols.py +6 -5
  48. waldiez/exporting/core/result.py +25 -28
  49. waldiez/exporting/core/types.py +11 -10
  50. waldiez/exporting/core/utils/llm_config.py +4 -4
  51. waldiez/exporting/core/validation.py +10 -11
  52. waldiez/exporting/flow/execution_generator.py +99 -10
  53. waldiez/exporting/flow/exporter.py +2 -2
  54. waldiez/exporting/flow/factory.py +2 -2
  55. waldiez/exporting/flow/file_generator.py +4 -2
  56. waldiez/exporting/flow/merger.py +5 -3
  57. waldiez/exporting/flow/orchestrator.py +72 -2
  58. waldiez/exporting/flow/utils/common.py +6 -6
  59. waldiez/exporting/flow/utils/importing.py +7 -8
  60. waldiez/exporting/flow/utils/linting.py +25 -9
  61. waldiez/exporting/flow/utils/logging.py +5 -77
  62. waldiez/exporting/models/exporter.py +8 -8
  63. waldiez/exporting/models/processor.py +5 -5
  64. waldiez/exporting/tools/exporter.py +2 -2
  65. waldiez/exporting/tools/processor.py +7 -4
  66. waldiez/io/__init__.py +11 -5
  67. waldiez/io/_ws.py +12 -6
  68. waldiez/io/models/constants.py +10 -10
  69. waldiez/io/models/content/audio.py +1 -0
  70. waldiez/io/models/content/base.py +20 -18
  71. waldiez/io/models/content/file.py +1 -0
  72. waldiez/io/models/content/image.py +1 -0
  73. waldiez/io/models/content/text.py +1 -0
  74. waldiez/io/models/content/video.py +1 -0
  75. waldiez/io/models/user_input.py +10 -5
  76. waldiez/io/models/user_response.py +17 -16
  77. waldiez/io/mqtt.py +18 -31
  78. waldiez/io/redis.py +18 -22
  79. waldiez/io/structured.py +122 -70
  80. waldiez/io/utils.py +19 -10
  81. waldiez/io/ws.py +7 -3
  82. waldiez/logger.py +16 -3
  83. waldiez/models/agents/__init__.py +3 -0
  84. waldiez/models/agents/agent/agent.py +25 -17
  85. waldiez/models/agents/agent/agent_data.py +25 -22
  86. waldiez/models/agents/agent/code_execution.py +9 -11
  87. waldiez/models/agents/agent/termination_message.py +10 -12
  88. waldiez/models/agents/agent/update_system_message.py +2 -4
  89. waldiez/models/agents/agents.py +8 -8
  90. waldiez/models/agents/assistant/assistant.py +6 -3
  91. waldiez/models/agents/assistant/assistant_data.py +2 -2
  92. waldiez/models/agents/captain/captain_agent.py +7 -4
  93. waldiez/models/agents/captain/captain_agent_data.py +5 -7
  94. waldiez/models/agents/doc_agent/doc_agent.py +7 -4
  95. waldiez/models/agents/doc_agent/doc_agent_data.py +9 -10
  96. waldiez/models/agents/doc_agent/rag_query_engine.py +10 -12
  97. waldiez/models/agents/extra_requirements.py +3 -3
  98. waldiez/models/agents/group_manager/group_manager.py +12 -7
  99. waldiez/models/agents/group_manager/group_manager_data.py +13 -12
  100. waldiez/models/agents/group_manager/speakers.py +17 -19
  101. waldiez/models/agents/rag_user_proxy/rag_user_proxy.py +7 -4
  102. waldiez/models/agents/rag_user_proxy/rag_user_proxy_data.py +4 -1
  103. waldiez/models/agents/rag_user_proxy/retrieve_config.py +69 -63
  104. waldiez/models/agents/rag_user_proxy/vector_db_config.py +19 -19
  105. waldiez/models/agents/reasoning/reasoning_agent.py +7 -4
  106. waldiez/models/agents/reasoning/reasoning_agent_data.py +3 -2
  107. waldiez/models/agents/reasoning/reasoning_agent_reason_config.py +8 -8
  108. waldiez/models/agents/user_proxy/user_proxy.py +6 -3
  109. waldiez/models/agents/user_proxy/user_proxy_data.py +1 -1
  110. waldiez/models/chat/chat.py +28 -20
  111. waldiez/models/chat/chat_data.py +22 -21
  112. waldiez/models/chat/chat_message.py +9 -9
  113. waldiez/models/chat/chat_nested.py +9 -9
  114. waldiez/models/chat/chat_summary.py +6 -6
  115. waldiez/models/common/__init__.py +2 -0
  116. waldiez/models/common/ag2_version.py +2 -0
  117. waldiez/models/common/base.py +2 -0
  118. waldiez/models/common/dict_utils.py +8 -6
  119. waldiez/models/common/handoff.py +20 -17
  120. waldiez/models/common/method_utils.py +9 -7
  121. waldiez/models/common/naming.py +49 -0
  122. waldiez/models/flow/flow.py +11 -6
  123. waldiez/models/flow/flow_data.py +23 -17
  124. waldiez/models/flow/info.py +3 -3
  125. waldiez/models/flow/naming.py +2 -1
  126. waldiez/models/model/_aws.py +11 -13
  127. waldiez/models/model/_llm.py +8 -0
  128. waldiez/models/model/_price.py +2 -4
  129. waldiez/models/model/extra_requirements.py +1 -3
  130. waldiez/models/model/model.py +2 -2
  131. waldiez/models/model/model_data.py +21 -21
  132. waldiez/models/tool/extra_requirements.py +2 -4
  133. waldiez/models/tool/predefined/_duckduckgo.py +1 -0
  134. waldiez/models/tool/predefined/_email.py +4 -0
  135. waldiez/models/tool/predefined/_google.py +1 -0
  136. waldiez/models/tool/predefined/_perplexity.py +2 -1
  137. waldiez/models/tool/predefined/_searxng.py +2 -1
  138. waldiez/models/tool/predefined/_tavily.py +1 -0
  139. waldiez/models/tool/predefined/_wikipedia.py +2 -1
  140. waldiez/models/tool/predefined/_youtube.py +1 -0
  141. waldiez/models/tool/tool.py +8 -5
  142. waldiez/models/tool/tool_data.py +2 -2
  143. waldiez/models/waldiez.py +152 -4
  144. waldiez/runner.py +11 -5
  145. waldiez/running/async_utils.py +192 -0
  146. waldiez/running/base_runner.py +155 -241
  147. waldiez/running/dir_utils.py +52 -0
  148. waldiez/running/environment.py +10 -44
  149. waldiez/running/events_mixin.py +252 -0
  150. waldiez/running/exceptions.py +20 -0
  151. waldiez/running/gen_seq_diagram.py +18 -15
  152. waldiez/running/io_utils.py +216 -0
  153. waldiez/running/protocol.py +11 -5
  154. waldiez/running/requirements_mixin.py +65 -0
  155. waldiez/running/results_mixin.py +926 -0
  156. waldiez/running/standard_runner.py +24 -27
  157. waldiez/running/step_by_step/breakpoints_mixin.py +503 -47
  158. waldiez/running/step_by_step/command_handler.py +154 -0
  159. waldiez/running/step_by_step/events_processor.py +379 -0
  160. waldiez/running/step_by_step/step_by_step_models.py +425 -41
  161. waldiez/running/step_by_step/step_by_step_runner.py +437 -382
  162. waldiez/running/subprocess_runner/__base__.py +13 -8
  163. waldiez/running/subprocess_runner/_async_runner.py +6 -4
  164. waldiez/running/subprocess_runner/_sync_runner.py +11 -6
  165. waldiez/running/subprocess_runner/runner.py +48 -23
  166. waldiez/running/timeline_processor.py +1 -1
  167. waldiez/utils/__init__.py +2 -0
  168. waldiez/utils/conflict_checker.py +4 -4
  169. waldiez/utils/python_manager.py +415 -0
  170. waldiez/ws/__init__.py +8 -7
  171. waldiez/ws/_file_handler.py +18 -20
  172. waldiez/ws/_mock.py +75 -0
  173. waldiez/ws/cli.py +58 -10
  174. waldiez/ws/client_manager.py +77 -53
  175. waldiez/ws/errors.py +3 -0
  176. waldiez/ws/models.py +61 -53
  177. waldiez/ws/reloader.py +33 -4
  178. waldiez/ws/server.py +121 -52
  179. waldiez/ws/session_manager.py +8 -9
  180. waldiez/ws/session_stats.py +1 -1
  181. waldiez/ws/utils.py +33 -5
  182. {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/METADATA +107 -109
  183. waldiez-0.6.1.dist-info/RECORD +254 -0
  184. waldiez/running/post_run.py +0 -180
  185. waldiez/running/pre_run.py +0 -159
  186. waldiez/running/run_results.py +0 -14
  187. waldiez/running/utils.py +0 -511
  188. waldiez-0.5.10.dist-info/RECORD +0 -248
  189. {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/WHEEL +0 -0
  190. {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/entry_points.txt +0 -0
  191. {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/licenses/LICENSE +0 -0
  192. {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/licenses/NOTICE.md +0 -0
@@ -0,0 +1,216 @@
1
+ # SPDX-License-Identifier: Apache-2.0.
2
+ # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+
4
+ # pyright: reportMissingTypeStubs=false
5
+ """Common utilities for the waldiez runner."""
6
+
7
+ import asyncio
8
+ import sys
9
+ import traceback
10
+
11
+ # noinspection PyProtectedMember
12
+ from dataclasses import dataclass
13
+ from getpass import getpass
14
+ from pathlib import Path
15
+ from typing import (
16
+ Any,
17
+ Callable,
18
+ )
19
+
20
+
21
+ @dataclass
22
+ class ProcessSetup:
23
+ """Container for subprocess setup data."""
24
+
25
+ temp_dir: Path
26
+ file_path: Path
27
+ old_vars: dict[str, str]
28
+ skip_mmd: bool
29
+
30
+
31
+ async def input_async(prompt: str, *, password: bool = False) -> str:
32
+ """Asynchronous input function.
33
+
34
+ Parameters
35
+ ----------
36
+ prompt : str
37
+ The prompt to display to the user.
38
+ password : bool, optional
39
+ Whether to hide input (password mode), by default False.
40
+
41
+ Returns
42
+ -------
43
+ str
44
+ The user input.
45
+ """
46
+ if password:
47
+ try:
48
+ return await asyncio.to_thread(getpass, prompt)
49
+ except EOFError:
50
+ return ""
51
+ try:
52
+ return await asyncio.to_thread(input, prompt)
53
+ except EOFError:
54
+ return ""
55
+
56
+
57
+ def input_sync(prompt: str, *, password: bool = False) -> str:
58
+ """Input function (synchronous).
59
+
60
+ Parameters
61
+ ----------
62
+ prompt : str
63
+ The prompt to display to the user.
64
+ password : bool, optional
65
+ Whether to hide input (password mode), by default False.
66
+
67
+ Returns
68
+ -------
69
+ str
70
+ The user input.
71
+ """
72
+ if password:
73
+ try:
74
+ return getpass(prompt)
75
+ except EOFError:
76
+ return ""
77
+ try:
78
+ return input(prompt)
79
+ except EOFError:
80
+ return ""
81
+
82
+
83
+ # pylint: disable=import-outside-toplevel,too-complex
84
+ def get_printer() -> Callable[..., None]: # noqa: C901
85
+ """Get the printer function.
86
+
87
+ Returns
88
+ -------
89
+ Callable[..., None]
90
+ The printer function that handles Unicode encoding errors gracefully.
91
+ """
92
+ try:
93
+ # noinspection PyUnresolvedReferences
94
+ from autogen.io import IOStream # type: ignore
95
+
96
+ printer = IOStream.get_default().print
97
+ except ImportError: # pragma: no cover
98
+ # Fallback to standard print if autogen is not available
99
+ printer = print
100
+
101
+ # noinspection PyBroadException,TryExceptPass
102
+ def safe_printer(*args: Any, **kwargs: Any) -> None: # noqa: C901
103
+ """Safe printer that handles Unicode encoding errors.
104
+
105
+ Parameters
106
+ ----------
107
+ *args : Any
108
+ Arguments to pass to the printer
109
+ **kwargs : Any
110
+ Keyword arguments to pass to the printer
111
+ """
112
+ # pylint: disable=broad-exception-caught,too-many-try-statements
113
+ try:
114
+ printer(*args, **kwargs)
115
+ except (UnicodeEncodeError, UnicodeDecodeError):
116
+ # First fallback: try to get a safe string representation
117
+ try:
118
+ msg, flush = get_what_to_print(*args, **kwargs)
119
+ # Convert problematic characters to safe representations
120
+ safe_msg = msg.encode("utf-8", errors="replace").decode("utf-8")
121
+ printer(safe_msg, end="", flush=flush)
122
+ except (UnicodeEncodeError, UnicodeDecodeError):
123
+ # Second fallback: use built-in print with safe encoding
124
+ try:
125
+ # Convert args to safe string representations
126
+ safe_args: list[str] = []
127
+ for arg in args:
128
+ try:
129
+ safe_args.append(
130
+ str(arg)
131
+ .encode("utf-8", errors="replace")
132
+ .decode("utf-8")
133
+ )
134
+ except (
135
+ UnicodeEncodeError,
136
+ UnicodeDecodeError,
137
+ ): # pragma: no cover
138
+ safe_args.append(repr(arg))
139
+
140
+ # Use built-in print instead of the custom printer
141
+ print(*safe_args, **kwargs)
142
+
143
+ except Exception:
144
+ # Final fallback: write directly to stderr buffer
145
+ error_msg = (
146
+ "Could not print the message due to encoding issues.\n"
147
+ )
148
+ to_sys_stderr(error_msg)
149
+ except Exception as e:
150
+ # Handle any other unexpected errors
151
+ traceback.print_exc()
152
+ error_msg = f"Unexpected error in printer: {str(e)}\n"
153
+ to_sys_stderr(error_msg)
154
+
155
+ return safe_printer
156
+
157
+
158
+ def to_sys_stderr(msg: str) -> None:
159
+ """Write a message to sys.stderr.
160
+
161
+ Parameters
162
+ ----------
163
+ msg : str
164
+ The message to write to stderr.
165
+ """
166
+ # pylint: disable=broad-exception-caught
167
+ # noinspection TryExceptPass,PyBroadException
168
+ try:
169
+ if hasattr(sys.stderr, "buffer"):
170
+ sys.stderr.buffer.write(msg.encode("utf-8", errors="replace"))
171
+ sys.stderr.buffer.flush()
172
+ else: # pragma: no cover
173
+ sys.stderr.write(msg)
174
+ sys.stderr.flush()
175
+ except Exception: # pragma: no cover
176
+ pass
177
+
178
+
179
+ def get_what_to_print(*args: Any, **kwargs: Any) -> tuple[str, bool]:
180
+ """Extract message and flush flag from print arguments.
181
+
182
+ Parameters
183
+ ----------
184
+ *args : Any
185
+ Arguments to print
186
+ **kwargs : Any
187
+ Keyword arguments for print function
188
+
189
+ Returns
190
+ -------
191
+ tuple[str, bool]
192
+ Message to print and flush flag
193
+ """
194
+ # Convert all args to strings and join with spaces (like print does)
195
+ msg = " ".join(str(arg) for arg in args)
196
+
197
+ # Handle sep parameter
198
+ sep = kwargs.get("sep", " ")
199
+ if isinstance(sep, bytes): # pragma: no cover
200
+ sep = sep.decode("utf-8", errors="replace")
201
+ if len(args) > 1:
202
+ msg = sep.join(str(arg) for arg in args)
203
+
204
+ # Handle end parameter
205
+ end = kwargs.get("end", "\n")
206
+ if isinstance(end, bytes): # pragma: no cover
207
+ end = end.decode("utf-8", errors="replace")
208
+ msg += end
209
+
210
+ # Handle flush parameter
211
+ flush = kwargs.get("flush", False)
212
+ # noinspection PyUnreachableCode
213
+ if not isinstance(flush, bool): # pragma: no cover
214
+ flush = False
215
+
216
+ return msg, flush
@@ -15,7 +15,7 @@ class WaldiezRunnerProtocol(Protocol):
15
15
  self,
16
16
  output_file: Path,
17
17
  uploads_root: Path | None,
18
- ) -> Path: # pyright: ignore
18
+ ) -> Path:
19
19
  """Actions to perform before running the flow.
20
20
 
21
21
  Parameters
@@ -35,7 +35,7 @@ class WaldiezRunnerProtocol(Protocol):
35
35
  self,
36
36
  output_file: Path,
37
37
  uploads_root: Path | None,
38
- ) -> Path: # pyright: ignore
38
+ ) -> Path:
39
39
  """Asynchronously perform actions before running the flow.
40
40
 
41
41
  Parameters
@@ -136,18 +136,21 @@ class WaldiezRunnerProtocol(Protocol):
136
136
  def after_run(
137
137
  self,
138
138
  results: list[dict[str, Any]],
139
+ error: BaseException | None,
139
140
  output_file: Path,
140
141
  uploads_root: Path | None,
141
142
  temp_dir: Path,
142
143
  skip_mmd: bool,
143
144
  skip_timeline: bool,
144
- ) -> None:
145
+ ) -> Path | None:
145
146
  """Actions to perform after running the flow.
146
147
 
147
148
  Parameters
148
149
  ----------
149
150
  results : list[dict[str, Any]]
150
151
  The results of the run.
152
+ error : BaseException | None
153
+ Optional error during the run.
151
154
  output_file : Path
152
155
  The path to the output file.
153
156
  uploads_root : Path | None
@@ -163,18 +166,21 @@ class WaldiezRunnerProtocol(Protocol):
163
166
  async def a_after_run(
164
167
  self,
165
168
  results: list[dict[str, Any]],
169
+ error: BaseException | None,
166
170
  output_file: Path,
167
171
  uploads_root: Path | None,
168
172
  temp_dir: Path,
169
173
  skip_mmd: bool,
170
174
  skip_timeline: bool,
171
- ) -> None:
175
+ ) -> Path | None:
172
176
  """Asynchronously perform actions after running the flow.
173
177
 
174
178
  Parameters
175
179
  ----------
176
180
  results : list[dict[str, Any]]
177
181
  The results of the run.
182
+ error : BaseException | None
183
+ Optional error during the run.
178
184
  output_file : Path
179
185
  The path to the output file.
180
186
  uploads_root : Path | None
@@ -187,7 +193,7 @@ class WaldiezRunnerProtocol(Protocol):
187
193
  Whether to skip generating the timeline JSON.
188
194
  """
189
195
 
190
- def is_running(self) -> bool: # pyright: ignore
196
+ def is_running(self) -> bool:
191
197
  """Check if the runner is currently running.
192
198
 
193
199
  Returns
@@ -0,0 +1,65 @@
1
+ # SPDX-License-Identifier: Apache-2.0.
2
+ # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+
4
+ # pyright: reportUninitializedInstanceVariable=false
5
+ """Actions to perform before running the flow."""
6
+
7
+ import sys
8
+ from typing import Callable
9
+
10
+ from waldiez.models import Waldiez
11
+ from waldiez.utils.python_manager import PythonManager
12
+
13
+ from .environment import refresh_environment
14
+
15
+
16
+ class RequirementsMixin:
17
+ """Mixin class to handle requirements installation."""
18
+
19
+ _waldiez: Waldiez
20
+ _called_install_requirements: bool
21
+ print: Callable[..., None]
22
+
23
+ def __init__(self) -> None:
24
+ """Initialize the instance."""
25
+ self._python_manager = PythonManager()
26
+
27
+ def gather_requirements(self) -> set[str]:
28
+ """Gather extra requirements to install before running the flow.
29
+
30
+ Returns
31
+ -------
32
+ set[str]
33
+ A set of requirements that are not already installed and do not
34
+ include 'waldiez' in their name.
35
+ """
36
+ extra_requirements = {
37
+ req
38
+ for req in self._waldiez.requirements
39
+ if req not in sys.modules and "waldiez" not in req
40
+ }
41
+ if "python-dotenv" not in extra_requirements: # pragma: no branch
42
+ extra_requirements.add("python-dotenv")
43
+ return extra_requirements
44
+
45
+ def install_requirements(self) -> None:
46
+ """Install the requirements for the flow."""
47
+ if not self._called_install_requirements: # pragma: no branch
48
+ self._called_install_requirements = True
49
+ extra_requirements = self.gather_requirements()
50
+ if extra_requirements: # pragma: no branch
51
+ self._python_manager.pip_install(
52
+ extra_requirements, printer=self.print
53
+ )
54
+ refresh_environment()
55
+
56
+ async def a_install_requirements(self) -> None:
57
+ """Install the requirements for the flow asynchronously."""
58
+ if not self._called_install_requirements: # pragma: no branch
59
+ self._called_install_requirements = True
60
+ extra_requirements = self.gather_requirements()
61
+ if extra_requirements: # pragma: no branch
62
+ await self._python_manager.a_pip_install(
63
+ extra_requirements, printer=self.print
64
+ )
65
+ refresh_environment()