waldiez 0.1.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 (94) hide show
  1. waldiez/__init__.py +15 -0
  2. waldiez/__main__.py +6 -0
  3. waldiez/_version.py +3 -0
  4. waldiez/cli.py +162 -0
  5. waldiez/exporter.py +293 -0
  6. waldiez/exporting/__init__.py +14 -0
  7. waldiez/exporting/agents/__init__.py +5 -0
  8. waldiez/exporting/agents/agent.py +229 -0
  9. waldiez/exporting/agents/agent_skills.py +67 -0
  10. waldiez/exporting/agents/code_execution.py +67 -0
  11. waldiez/exporting/agents/group_manager.py +209 -0
  12. waldiez/exporting/agents/llm_config.py +53 -0
  13. waldiez/exporting/agents/rag_user/__init__.py +5 -0
  14. waldiez/exporting/agents/rag_user/chroma_utils.py +134 -0
  15. waldiez/exporting/agents/rag_user/mongo_utils.py +83 -0
  16. waldiez/exporting/agents/rag_user/pgvector_utils.py +93 -0
  17. waldiez/exporting/agents/rag_user/qdrant_utils.py +112 -0
  18. waldiez/exporting/agents/rag_user/rag_user.py +165 -0
  19. waldiez/exporting/agents/rag_user/vector_db.py +119 -0
  20. waldiez/exporting/agents/teachability.py +37 -0
  21. waldiez/exporting/agents/termination_message.py +45 -0
  22. waldiez/exporting/chats/__init__.py +14 -0
  23. waldiez/exporting/chats/chats.py +46 -0
  24. waldiez/exporting/chats/helpers.py +395 -0
  25. waldiez/exporting/chats/nested.py +264 -0
  26. waldiez/exporting/flow/__init__.py +5 -0
  27. waldiez/exporting/flow/def_main.py +37 -0
  28. waldiez/exporting/flow/flow.py +185 -0
  29. waldiez/exporting/models/__init__.py +193 -0
  30. waldiez/exporting/skills/__init__.py +128 -0
  31. waldiez/exporting/utils/__init__.py +34 -0
  32. waldiez/exporting/utils/comments.py +136 -0
  33. waldiez/exporting/utils/importing.py +267 -0
  34. waldiez/exporting/utils/logging_utils.py +203 -0
  35. waldiez/exporting/utils/method_utils.py +35 -0
  36. waldiez/exporting/utils/naming.py +127 -0
  37. waldiez/exporting/utils/object_string.py +81 -0
  38. waldiez/io_stream.py +181 -0
  39. waldiez/models/__init__.py +107 -0
  40. waldiez/models/agents/__init__.py +65 -0
  41. waldiez/models/agents/agent/__init__.py +21 -0
  42. waldiez/models/agents/agent/agent.py +190 -0
  43. waldiez/models/agents/agent/agent_data.py +162 -0
  44. waldiez/models/agents/agent/code_execution.py +71 -0
  45. waldiez/models/agents/agent/linked_skill.py +30 -0
  46. waldiez/models/agents/agent/nested_chat.py +73 -0
  47. waldiez/models/agents/agent/teachability.py +68 -0
  48. waldiez/models/agents/agent/termination_message.py +167 -0
  49. waldiez/models/agents/agents.py +129 -0
  50. waldiez/models/agents/assistant/__init__.py +6 -0
  51. waldiez/models/agents/assistant/assistant.py +41 -0
  52. waldiez/models/agents/assistant/assistant_data.py +29 -0
  53. waldiez/models/agents/group_manager/__init__.py +19 -0
  54. waldiez/models/agents/group_manager/group_manager.py +87 -0
  55. waldiez/models/agents/group_manager/group_manager_data.py +91 -0
  56. waldiez/models/agents/group_manager/speakers.py +211 -0
  57. waldiez/models/agents/rag_user/__init__.py +26 -0
  58. waldiez/models/agents/rag_user/rag_user.py +58 -0
  59. waldiez/models/agents/rag_user/rag_user_data.py +32 -0
  60. waldiez/models/agents/rag_user/retrieve_config.py +592 -0
  61. waldiez/models/agents/rag_user/vector_db_config.py +162 -0
  62. waldiez/models/agents/user_proxy/__init__.py +6 -0
  63. waldiez/models/agents/user_proxy/user_proxy.py +41 -0
  64. waldiez/models/agents/user_proxy/user_proxy_data.py +30 -0
  65. waldiez/models/chat/__init__.py +22 -0
  66. waldiez/models/chat/chat.py +129 -0
  67. waldiez/models/chat/chat_data.py +326 -0
  68. waldiez/models/chat/chat_message.py +304 -0
  69. waldiez/models/chat/chat_nested.py +160 -0
  70. waldiez/models/chat/chat_summary.py +110 -0
  71. waldiez/models/common/__init__.py +38 -0
  72. waldiez/models/common/base.py +63 -0
  73. waldiez/models/common/method_utils.py +165 -0
  74. waldiez/models/flow/__init__.py +9 -0
  75. waldiez/models/flow/flow.py +302 -0
  76. waldiez/models/flow/flow_data.py +87 -0
  77. waldiez/models/model/__init__.py +11 -0
  78. waldiez/models/model/model.py +169 -0
  79. waldiez/models/model/model_data.py +86 -0
  80. waldiez/models/skill/__init__.py +9 -0
  81. waldiez/models/skill/skill.py +129 -0
  82. waldiez/models/skill/skill_data.py +37 -0
  83. waldiez/models/waldiez.py +301 -0
  84. waldiez/py.typed +0 -0
  85. waldiez/runner.py +304 -0
  86. waldiez/stream/__init__.py +7 -0
  87. waldiez/stream/consumer.py +139 -0
  88. waldiez/stream/provider.py +339 -0
  89. waldiez/stream/server.py +412 -0
  90. waldiez-0.1.0.dist-info/METADATA +181 -0
  91. waldiez-0.1.0.dist-info/RECORD +94 -0
  92. waldiez-0.1.0.dist-info/WHEEL +4 -0
  93. waldiez-0.1.0.dist-info/entry_points.txt +2 -0
  94. waldiez-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,203 @@
1
+ """Logging related string generation functions.
2
+
3
+ Functions
4
+ ---------
5
+ get_logging_start_string
6
+ Get the string to start logging.
7
+ get_logging_stop_string
8
+ Get the string to stop logging.
9
+ get_sqlite_to_csv_string
10
+ Get the sqlite to csv conversion code string.
11
+ get_sqlite_to_csv_call_string
12
+ Get the string to call the sqlite to csv conversion.
13
+ """
14
+
15
+
16
+ # Check issue:
17
+ # https://github.com/microsoft/autogen/issues/2286
18
+ # we cannot log new agents if they have code_execution enabled
19
+ # we get `Path` is not JSON serializable (on code_executor)
20
+ # pylint: disable=inconsistent-quotes
21
+ def get_logging_start_string(tabs: int = 0) -> str:
22
+ """Get the logging start string.
23
+
24
+ Parameters
25
+ ----------
26
+ tabs : int, optional
27
+ The number of tabs to use for indentation, by default 0
28
+
29
+ Returns
30
+ -------
31
+ str
32
+ The logging start string.
33
+
34
+ Example
35
+ -------
36
+ ```python
37
+ >>> get_logging_start_string()
38
+ runtime_logging.start(
39
+ logger_type="sqlite",
40
+ config={"dbname": "flow.db"},
41
+ )
42
+ ```
43
+ """
44
+ tab = " " * tabs
45
+ content = f"{tab}runtime_logging.start(\n"
46
+ content += f'{tab} logger_type="sqlite",\n'
47
+ content += f'{tab} config={{"dbname": "flow.db"}},\n'
48
+ content += f"{tab})\n"
49
+ return content
50
+
51
+
52
+ def get_logging_stop_string(tabs: int = 0) -> str:
53
+ """Get the logging stop string.
54
+
55
+ Parameters
56
+ ----------
57
+ tabs : int, optional
58
+ The number of tabs to use for indentation, by default 0
59
+
60
+ Returns
61
+ -------
62
+ str
63
+ The logging stop string
64
+
65
+ Example
66
+ -------
67
+ ```python
68
+ >>> get_logging_stop_string()
69
+ runtime_logging.stop()
70
+ ```
71
+ """
72
+ tab = " " * tabs
73
+ return f"{tab}runtime_logging.stop()\n"
74
+
75
+
76
+ # pylint: disable=differing-param-doc,differing-type-doc
77
+ def get_sqlite_to_csv_string() -> str:
78
+ """Get the sqlite to csv conversion code string.
79
+
80
+ Returns
81
+ -------
82
+ str
83
+ The sqlite to csv conversion code string.
84
+
85
+ Example
86
+ -------
87
+ ```python
88
+ >>> get_sqlite_to_csv_string()
89
+ def sqlite_to_csv(dbname: str, table: str, csv_file: str) -> None:
90
+ \"\"\"Convert a sqlite table to a csv file.
91
+
92
+ Parameters
93
+ ----------
94
+ dbname : str
95
+ The sqlite database name.
96
+ table : str
97
+ The table name.
98
+ csv_file : str
99
+ The csv file name.
100
+ \"\"\"
101
+ conn = sqlite3.connect(dbname)
102
+ query = f"SELECT * FROM {table}" # nosec
103
+ cursor = conn.execute(query)
104
+ rows = cursor.fetchall()
105
+ column_names = [description[0] for description in cursor.description]
106
+ data = [dict(zip(column_names, row)) for row in rows]
107
+ conn.close()
108
+ with open(csv_file, "w", newline="", encoding="utf-8") as file:
109
+ _csv_writer = csv.DictWriter(file, fieldnames=column_names)
110
+ _csv_writer.writeheader()
111
+ _csv_writer.writerows(data)
112
+ ```
113
+ """
114
+ content = "\n\n"
115
+ content += (
116
+ "def sqlite_to_csv(dbname: str, table: str, csv_file: str) -> None:\n"
117
+ )
118
+ content += ' """Convert a sqlite table to a csv file.\n\n'
119
+ content += " Parameters\n"
120
+ content += " ----------\n"
121
+ content += " dbname : str\n"
122
+ content += " The sqlite database name.\n"
123
+ content += " table : str\n"
124
+ content += " The table name.\n"
125
+ content += " csv_file : str\n"
126
+ content += " The csv file name.\n"
127
+ content += ' """\n'
128
+ content += " conn = sqlite3.connect(dbname)\n"
129
+ content += ' query = f"SELECT * FROM {table}" # nosec\n'
130
+ content += " try:\n"
131
+ content += " cursor = conn.execute(query)\n"
132
+ content += " except sqlite3.OperationalError:\n"
133
+ content += " conn.close()\n"
134
+ content += " return\n"
135
+ content += " rows = cursor.fetchall()\n"
136
+ content += " column_names = [description[0] for description "
137
+ content += "in cursor.description]\n"
138
+ content += " data = [dict(zip(column_names, row)) for row in rows]\n"
139
+ content += " conn.close()\n"
140
+ content += (
141
+ ' with open(csv_file, "w", newline="", encoding="utf-8") as file:\n'
142
+ )
143
+ content += (
144
+ " _csv_writer = csv.DictWriter(file, fieldnames=column_names)\n"
145
+ )
146
+ content += " _csv_writer.writeheader()\n"
147
+ content += " _csv_writer.writerows(data)\n"
148
+ content += "\n\n"
149
+ return content
150
+
151
+
152
+ def get_sqlite_to_csv_call_string(tabs: int = 0) -> str:
153
+ """Get the sqlite to csv conversion call string.
154
+
155
+ Parameters
156
+ ----------
157
+ tabs : int, optional
158
+ The number of tabs to use for indentation, by default 0
159
+
160
+ Returns
161
+ -------
162
+ str
163
+ The sqlite to csv conversion call string.
164
+
165
+ Example
166
+ -------
167
+ ```python
168
+ >>> get_sqlite_to_csv_call_string()
169
+ if not os.path.exists("logs"):
170
+ os.makedirs("logs")
171
+ for table in [
172
+ "chat_completions",
173
+ "agents",
174
+ "oai_wrappers",
175
+ "oai_clients",
176
+ "version",
177
+ "events",
178
+ "function_calls",
179
+ ]:
180
+ dest = os.path.join("logs", f"{table}.csv")
181
+ sqlite_to_csv("flow.db", table, dest)
182
+ ```
183
+ """
184
+ table_names = [
185
+ "chat_completions",
186
+ "agents",
187
+ "oai_wrappers",
188
+ "oai_clients",
189
+ "version",
190
+ "events",
191
+ "function_calls",
192
+ ]
193
+ tab = " " * tabs
194
+ content = ""
195
+ content += tab + 'if not os.path.exists("logs"):\n'
196
+ content += tab + ' os.makedirs("logs")\n'
197
+ content += tab + "for table in [\n"
198
+ for table in table_names:
199
+ content += tab + f' "{table}",\n'
200
+ content += tab + "]:\n"
201
+ content += tab + ' dest = os.path.join("logs", f"{table}.csv")\n'
202
+ content += tab + ' sqlite_to_csv("flow.db", table, dest)\n'
203
+ return content
@@ -0,0 +1,35 @@
1
+ """Method related string generation utilities."""
2
+
3
+ from waldiez.models import METHOD_ARGS, WaldiezMethodName
4
+
5
+
6
+ def get_method_string(
7
+ method_name: WaldiezMethodName, renamed_method_name: str, method_body: str
8
+ ) -> str:
9
+ """Get a function string.
10
+
11
+ Parameters
12
+ ----------
13
+ method_name : WaldiezMethodName
14
+ The method name.
15
+ renamed_method_name : str
16
+ The renamed method name.
17
+ method_body : str
18
+ The method body.
19
+
20
+ Returns
21
+ -------
22
+ str
23
+ The function string having the definition, type hints and body.
24
+ """
25
+ method_args = METHOD_ARGS[method_name]
26
+ content = f"def {renamed_method_name}("
27
+ if len(method_args) == 0:
28
+ content += "):"
29
+ else:
30
+ content += "\n"
31
+ for arg in method_args:
32
+ content += f" {arg},\n"
33
+ content += "):"
34
+ content += f"\n{method_body}"
35
+ return content
@@ -0,0 +1,127 @@
1
+ """Naming related string generation functions.
2
+
3
+ Functions
4
+ ---------
5
+ get_valid_python_variable_name
6
+ Make sure a string is a valid Python variable name.
7
+ get_valid_instance_name
8
+ Get a valid instance name.
9
+ get_escaped_string
10
+ Get a string with escaped quotes and newlines.
11
+ """
12
+
13
+ import re
14
+ from typing import Dict, Tuple
15
+
16
+
17
+ def get_valid_python_variable_name(
18
+ possible: str,
19
+ prefix: str = "w",
20
+ ) -> str:
21
+ """Get a valid Python variable name from a possible name.
22
+
23
+ Parameters
24
+ ----------
25
+ possible : str
26
+ The possible name.
27
+
28
+ prefix : str, optional
29
+ The prefix to use if the name starts with a digit or special character
30
+
31
+ Returns
32
+ -------
33
+ str
34
+ The valid Python variable name.
35
+ """
36
+
37
+ def replacement(match: re.Match[str]) -> str:
38
+ """Get the replacement for the match.
39
+
40
+ Parameters
41
+ ----------
42
+ match : re.Match[str]
43
+ The match.
44
+
45
+ Returns
46
+ -------
47
+ str
48
+ The replacement
49
+ """
50
+ if match.group(0) in ["->", "=>"]:
51
+ return "to"
52
+ if match.group(0) in ["<-", "<="]:
53
+ return "from"
54
+ if re.match(r"\W|^(?=\d)", match.group(0)):
55
+ return "_"
56
+ return match.group(0)
57
+
58
+ possible = re.sub(r"->|=>|<-|<=|\W|^(?=\d)", replacement, possible)[
59
+ :64
60
+ ].lower()
61
+
62
+ if not possible:
63
+ return prefix + "_"
64
+ if possible.startswith("_"):
65
+ return f"{prefix}{possible}"
66
+ if possible[0].isdigit():
67
+ return f"{prefix}_{possible}"
68
+ return possible
69
+
70
+
71
+ def get_valid_instance_name(
72
+ instance: Tuple[str, str],
73
+ current_names: Dict[str, str],
74
+ prefix: str = "w",
75
+ ) -> Dict[str, str]:
76
+ """Get a valid instance name.
77
+
78
+ If the instance id is already in the current names nothing is done.
79
+ If the name already exists in the current names,
80
+ the name is updated (with an index suffix).
81
+
82
+ Parameters
83
+ ----------
84
+ instance : Tuple[str, str]
85
+ The instance id and possible name.
86
+ current_names : Dict[str, str]
87
+ The current names.
88
+ prefix : str, optional
89
+ The prefix to use if the name starts with a digit,
90
+ if the name is already in the current names,
91
+ or if the name is already in the current names with an index suffix.
92
+
93
+ Returns
94
+ -------
95
+ Dict[str, str]
96
+ The updated names.
97
+ """
98
+ instance_id, possible_name = instance
99
+ if instance_id in current_names:
100
+ return current_names
101
+ new_names = current_names.copy()
102
+ name = get_valid_python_variable_name(possible_name, prefix)
103
+ if name in current_names.values():
104
+ name = f"{prefix}_{name}"
105
+ if name in current_names.values():
106
+ index = 1
107
+ while f"{name}_{index}" in current_names.values():
108
+ index += 1
109
+ name = f"{name}_{index}"
110
+ new_names[instance_id] = name
111
+ return new_names
112
+
113
+
114
+ def get_escaped_string(string: str) -> str:
115
+ """Get a string with escaped quotes and newlines.
116
+
117
+ Parameters
118
+ ----------
119
+ string : str
120
+ The original string.
121
+
122
+ Returns
123
+ -------
124
+ str
125
+ The escaped string.
126
+ """
127
+ return string.replace('"', '\\"').replace("\n", "\\n")
@@ -0,0 +1,81 @@
1
+ """Function to convert an object to a formatted string with indentation.
2
+
3
+ To be used with dicts and/or lists.
4
+ """
5
+
6
+ from typing import Any
7
+
8
+
9
+ def get_object_string(obj: Any, tabs: int = 1) -> str:
10
+ """Convert an object to a formatted string with given indentation.
11
+
12
+ Parameters
13
+ ----------
14
+ obj : Any
15
+ The object to convert.
16
+ tabs : int, optional
17
+ The number of tabs, by default 1.
18
+
19
+ Returns
20
+ -------
21
+ str
22
+ The formatted string.
23
+
24
+ Example
25
+ -------
26
+ ```python
27
+ >>> obj = {"a": 1, "b": [1, 2, 3]}
28
+ >>> get_object_string(obj)
29
+ {
30
+ "a": 1,
31
+ "b": [
32
+ 1,
33
+ 2,
34
+ 3
35
+ ]
36
+ }
37
+ >>> obj = {"a": 1, "b": [1, 2, 3], "c": {"d": 4}}
38
+ >>> get_object_string(obj, 2)
39
+ {
40
+ "a": 1,
41
+ "b": [
42
+ 1,
43
+ 2,
44
+ 3
45
+ ],
46
+ "c": {
47
+ "d": 4
48
+ }
49
+ }
50
+ ```
51
+ """
52
+ indent = " " * 4 * tabs # Number of spaces corresponding to the tabs
53
+ next_indent = (
54
+ " " * 4 * (tabs + 1)
55
+ ) # Number of spaces corresponding to the next tab level
56
+ if isinstance(obj, dict):
57
+ items = []
58
+ for key, value in obj.items():
59
+ items.append(
60
+ f'{next_indent}"{key}": {get_object_string(value, tabs + 1)}'
61
+ )
62
+ # python3.10? f-string expression part cannot include a backslash
63
+ items_string = ",\n".join(items)
64
+ to_return = "\n" + items_string + "\n" + indent
65
+ return f"{{{to_return}}}"
66
+ # return f'{{\n{",\n".join(items)}\n{indent}}}'
67
+ if isinstance(obj, list):
68
+ items = []
69
+ for item in obj:
70
+ items.append(f"{next_indent}{get_object_string(item, tabs + 1)}")
71
+ # python3.10? f-string expression part cannot include a backslash
72
+ items_string = ",\n".join(items)
73
+ to_return = "\n" + items_string + "\n" + indent
74
+ return f"[{to_return}]"
75
+
76
+ if isinstance(obj, str):
77
+ return f'"{obj}"'
78
+
79
+ if obj is None:
80
+ return "None"
81
+ return str(obj)
waldiez/io_stream.py ADDED
@@ -0,0 +1,181 @@
1
+ """Custom IOStream class to use with autogen.
2
+
3
+ It is meant to be used when we want to use custom
4
+ `print` and `input`. For example, when a websocket
5
+ is used to trigger a UI element that requires user input.
6
+ and sends back the user's input to the websocket. In the same
7
+ way, we can use it to forward what is meant to be printed.
8
+
9
+
10
+ We use:
11
+
12
+ - one tcp server to handle messaging between the clients
13
+ - one tcp client (provider) to set and forward the user's input
14
+ that we got elsewhere (e.g. from a websocket connection)
15
+ - one tcp client (consumer) to ask and get the input from the provider
16
+ """
17
+
18
+ import socket
19
+ from contextlib import closing
20
+ from typing import Any, Callable, Optional
21
+
22
+ from autogen.io import IOStream # type: ignore[import-untyped]
23
+
24
+ from .stream import TCPConsumer, TCPProvider, TCPServer
25
+
26
+
27
+ class WaldiezIOStream(IOStream):
28
+ """Custom IOStream class to handle the `print` and `input` functions."""
29
+
30
+ def __init__(
31
+ self,
32
+ port: int = 0,
33
+ input_timeout: float = 60,
34
+ print_function: Optional[Callable[..., None]] = None,
35
+ on_prompt_input: Optional[Callable[[str], None]] = None,
36
+ ) -> None:
37
+ """Initialize the IOStream.
38
+
39
+ Parameters
40
+ ----------
41
+ port : int, optional
42
+ The port to use, by default 0 (auto-assign).
43
+ input_timeout : float, optional
44
+ The input timeout, by default 60.
45
+ print_function : Optional[Callable[..., None]], optional
46
+ The print function to use, by default None.
47
+ on_prompt_input : Optional[Callable[[str], None]], optional
48
+ The function to call for getting an input, by default None.
49
+ """
50
+ self._print_function = print_function
51
+ if port == 0:
52
+ port = get_available_port()
53
+ self._port = port
54
+ self._input_timeout = input_timeout
55
+ self._server = TCPServer(port)
56
+ self._server.start()
57
+ self._provider = TCPProvider("localhost", port, response=None)
58
+ self._on_prompt_input = on_prompt_input
59
+
60
+ @property
61
+ def print_function(self) -> Optional[Callable[..., None]]:
62
+ """Get the print function."""
63
+ return self._print_function
64
+
65
+ def open(self) -> None:
66
+ """Start the server."""
67
+ if not self._server.is_running():
68
+ self._server.start()
69
+
70
+ def close(self) -> None:
71
+ """Stop the server and the provider."""
72
+ # pylint: disable=broad-except
73
+ if self._server.is_running():
74
+ try:
75
+ self._server.stop()
76
+ except BaseException: # pragma: no cover
77
+ pass
78
+ try:
79
+ self._provider.stop()
80
+ except BaseException: # pragma: no cover
81
+ pass
82
+
83
+ def __del__(self) -> None: # pragma: no cover
84
+ """Delete the instance."""
85
+ self.close()
86
+
87
+ def forward_input(self, input_data: str) -> None:
88
+ """Forward the user's input to the provider.
89
+
90
+ When we have the input data
91
+ e.g. from 'input(..)' or from a websocket connection,
92
+ we can forward it to the provider (the tcp client)
93
+ to make it available to the consumer (the other tcp client).
94
+ Parameters
95
+ ----------
96
+ input_data : str
97
+ The input data to forward.
98
+ """
99
+ if not self._provider.is_running():
100
+ self._provider.start()
101
+ self._provider.set_response(input_data)
102
+
103
+ def print(
104
+ self,
105
+ *objects: Any,
106
+ sep: str = " ",
107
+ end: str = "\n",
108
+ flush: bool = False,
109
+ ) -> None:
110
+ """Mock the print function.
111
+
112
+ Parameters
113
+ ----------
114
+ objects : Any
115
+ The objects to print.
116
+ sep : str, optional
117
+ The separator, by default " ".
118
+ end : str, optional
119
+ The end, by default a new line.
120
+ flush : bool, optional
121
+ Whether to flush, by default False.
122
+ """
123
+ print_function: Callable[..., None] = self.print_function or print
124
+ print_function(*objects, sep=sep, end=end, flush=flush)
125
+
126
+ def input(self, prompt: str = "", *, password: bool = False) -> str:
127
+ """Mock the input function.
128
+
129
+ Parameters
130
+ ----------
131
+ prompt : str, optional
132
+ The prompt to show, by default "".
133
+ password : bool, optional (not used)
134
+ Whether to show the input as password, by default False.
135
+
136
+ Returns
137
+ -------
138
+ str
139
+ The user's input.
140
+ """
141
+ _prompt = prompt or "Your input:"
142
+ if _prompt in (">", "> "):
143
+ _prompt = "Your input:"
144
+ if prompt:
145
+ if self._on_prompt_input:
146
+ self._on_prompt_input(_prompt)
147
+ self.print(_prompt, end="")
148
+ if not self._provider.is_running():
149
+ self._provider.start()
150
+ # wait for the provider to start
151
+ self._provider.wait(timeout=self._input_timeout)
152
+ if not self._provider.is_running(): # pragma: no cover
153
+ self.print(
154
+ "WARNING: Provider is not running. Was an input expected?"
155
+ )
156
+ return "\n"
157
+ consumer = TCPConsumer(
158
+ "localhost", self._port, timeout=self._input_timeout
159
+ )
160
+ consumer.start()
161
+ # send the prompt and wait for the response
162
+ consumer.send_prompt(_prompt)
163
+ response = consumer.get_response()
164
+ consumer.stop()
165
+ self._provider.stop()
166
+ # return the response or a line break (i.e. no input)
167
+ return response or "\n"
168
+
169
+
170
+ def get_available_port() -> int:
171
+ """Get an available port.
172
+
173
+ Returns
174
+ -------
175
+ int
176
+ Available port.
177
+ """
178
+ with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as soc:
179
+ soc.bind(("", 0))
180
+ soc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
181
+ return soc.getsockname()[1]