waldiez 0.1.6__py3-none-any.whl → 0.1.8__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/stream/server.py DELETED
@@ -1,412 +0,0 @@
1
- """Simple TCP server using twisted.
2
-
3
- It listens for connections from an input provider and an input consumer,
4
- and forwards messages between them.
5
- """
6
-
7
- # pylint: disable=import-outside-toplevel,no-member,reimported,unused-import,redefined-outer-name,invalid-name # noqa
8
- import logging
9
- import sys
10
- from threading import Thread
11
- from types import TracebackType
12
- from typing import Dict, Optional, Type, cast
13
-
14
- from twisted.internet.error import ReactorNotRestartable
15
- from twisted.internet.interfaces import IReactorCore
16
- from twisted.internet.protocol import Factory, Protocol, connectionDone
17
- from twisted.internet.tcp import Port
18
- from twisted.python.failure import Failure
19
-
20
- LOGGER = logging.getLogger("tcp::server")
21
- END_OF_MESSAGE = b"\r\n"
22
-
23
-
24
- class ServerProtocol(Protocol):
25
- """Server protocol."""
26
-
27
- factory: "ServerFactory"
28
-
29
- def set_factory(self, factory: "ServerFactory") -> None:
30
- """Set the factory.
31
-
32
- Parameters
33
- ----------
34
- factory : ServerFactory
35
- The factory to set.
36
- """
37
- self.factory = factory
38
-
39
- def connectionLost(self, reason: Failure = connectionDone) -> None:
40
- """Handle connection lost event.
41
-
42
- Parameters
43
- ----------
44
- reason : Failure, optional
45
- The reason for the connection loss, by default connectionDone
46
- """
47
- if self.factory.clients["provider"] == self:
48
- self.factory.clients["provider"] = None
49
- LOGGER.info("Input provider disconnected.")
50
- elif self.factory.clients["consumer"] == self:
51
- self.factory.clients["consumer"] = None
52
- LOGGER.info("Input consumer disconnected.")
53
- super().connectionLost(reason)
54
-
55
- def message_received(self, message: str) -> None:
56
- """Handle a message received event.
57
-
58
- Parameters
59
- ----------
60
- message : str
61
- The message received.
62
- """
63
- if message.startswith("REQUEST:"):
64
- prompt = message[len("REQUEST:") :]
65
- if prompt.endswith("\r\n"):
66
- prompt = prompt[: -(len("\r\n"))]
67
- prompt = prompt.strip()
68
- LOGGER.debug("Received request: %s", prompt)
69
- if self.factory.clients["provider"]:
70
- msg = f"PROVIDE:{prompt}" + "\r\n"
71
- transport = self.factory.clients["provider"].transport
72
- transport.write(msg.encode("utf-8")) # type: ignore
73
- else:
74
- LOGGER.error("No provider connected.")
75
- elif message.startswith("USE:"):
76
- response = message[len("USE:") :]
77
- if response.endswith("\r\n"):
78
- response = response[: -(len("\r\n"))]
79
- response = response.strip()
80
- LOGGER.debug("Received response: %s", response)
81
- if self.factory.clients["consumer"]:
82
- msg = f"INPUT:{response}" + "\r\n"
83
- transport = self.factory.clients["consumer"].transport
84
- transport.write(msg.encode("utf-8")) # type: ignore
85
- else:
86
- LOGGER.error("No consumer connected.")
87
-
88
- def dataReceived(self, data: bytes) -> None:
89
- """Handle a data received event.
90
-
91
- Parameters
92
- ----------
93
- data : bytes
94
- The data received.
95
- """
96
- # we might get multiple messages in one chunk
97
- # i.e. CONSUMER\r\nREQUEST:prompt\r\n
98
- message = data.decode("utf-8")
99
- if message in ("PROVIDER\r\n", "PROVIDER\n", "PROVIDER"):
100
- LOGGER.debug("Input provider connected.")
101
- self.factory.clients["provider"] = self
102
- return
103
- if message.startswith("CONSUMER\r\n"):
104
- LOGGER.debug("Input consumer connected.")
105
- self.factory.clients["consumer"] = self
106
- rest = message[len("CONSUMER\r\n") :]
107
- if rest:
108
- self.message_received(rest)
109
- return
110
- self.message_received(message)
111
-
112
-
113
- class ServerFactory(Factory):
114
- """Server factory."""
115
-
116
- protocol: "ServerProtocol" # type: ignore
117
- clients: Dict[str, Optional["ServerProtocol"]]
118
-
119
- def __init__(self) -> None:
120
- """Initialize the factory."""
121
- super().__init__()
122
- self.clients = {
123
- "provider": None,
124
- "consumer": None,
125
- }
126
-
127
- def buildProtocol(self, addr: str) -> "ServerProtocol":
128
- """Build the protocol.
129
-
130
- Parameters
131
- ----------
132
- addr : str
133
- The address (ignored)
134
-
135
- Returns
136
- -------
137
- ServerProtocol
138
- The factory's protocol.
139
- """
140
- self.protocol = ServerProtocol()
141
- self.protocol.set_factory(self)
142
- return self.protocol
143
-
144
-
145
- def get_reactor() -> IReactorCore:
146
- """Get the reactor from twisted.
147
-
148
- Returns
149
- -------
150
- IReactorCore
151
- The twisted's reactor
152
- """
153
- # dummy hack to allow restarting the reactor
154
- if "twisted.internet.reactor" in sys.modules:
155
- del sys.modules["twisted.internet.reactor"]
156
- import twisted.internet.error
157
- from twisted.internet import reactor # noqa
158
- from twisted.internet import default
159
-
160
- try:
161
- default.install()
162
- # pylint: disable=line-too-long
163
- except (
164
- twisted.internet.error.ReactorAlreadyInstalledError
165
- ): # pragma: no cover
166
- pass
167
- # cast it so mypy doesn't complain a lot
168
- reactor_cast = cast(IReactorCore, reactor)
169
- return reactor_cast
170
-
171
-
172
- class TCPServerThread(Thread):
173
- """Threaded TCP server."""
174
-
175
- reactor: Optional[IReactorCore] = None # noqa
176
- factory: Optional[Factory] = None # noqa
177
-
178
- def __init__(
179
- self,
180
- interface: str,
181
- port: int,
182
- timeout: Optional[float] = None,
183
- ) -> None:
184
- """Create a new TCP server.
185
-
186
- Parameters
187
- ----------
188
- interface : str
189
- Interface to listen on. Defaults to '' (all interfaces)
190
- port : int
191
- Port to listen on.
192
- timeout : Optional[float]
193
- Timeout for the server.
194
- """
195
- super().__init__(
196
- name="TCPServerThread",
197
- daemon=True,
198
- target=self.run,
199
- )
200
- from twisted.internet.endpoints import TCP4ServerEndpoint
201
-
202
- self.timeout = timeout
203
- self.reactor = get_reactor()
204
- self._port = port
205
- endpoint = TCP4ServerEndpoint( # type: ignore[no-untyped-call]
206
- self.reactor,
207
- port,
208
- interface=interface,
209
- )
210
- server_factory = ServerFactory()
211
- deferred = endpoint.listen(server_factory) # type: ignore
212
- deferred.addCallback(callback=self.on_start)
213
-
214
- @property
215
- def port(self) -> int:
216
- """Get the port."""
217
- return self._port
218
-
219
- def on_start(self, port: Port) -> None:
220
- """On connect callback.
221
-
222
- Parameters
223
- ----------
224
- port : Port
225
- The port to connect to.
226
- """
227
- socket = port.getHost() # type: ignore[no-untyped-call]
228
- LOGGER.debug(
229
- "listening on %s:%s",
230
- socket.host,
231
- socket.port,
232
- )
233
- self._port = socket.port
234
- self.factory = port.factory
235
-
236
- def run(self) -> None:
237
- """Start the server.
238
-
239
- Raises
240
- ------
241
- RuntimeError
242
- If reactor is not initialized
243
- """
244
- if self.reactor is None: # pragma: no cover (just for the linter)
245
- raise RuntimeError("reactor is not running")
246
- if not self.reactor.running:
247
- try:
248
- self.reactor.run(installSignalHandlers=False) # type: ignore
249
- except ReactorNotRestartable: # pragma: no cover
250
- self.reactor = get_reactor()
251
- self.reactor.run(installSignalHandlers=False) # type: ignore
252
-
253
-
254
- class ServerWrapper:
255
- """Server Wrapper."""
256
-
257
- server: TCPServerThread
258
- timeout: float
259
-
260
- def __init__(
261
- self,
262
- interface: str,
263
- port: int,
264
- timeout: Optional[float] = None,
265
- ) -> None:
266
- """Create a new TCP server.
267
-
268
- Parameters
269
- ----------
270
- interface : str
271
- Interface to listen on. Defaults to '' (all interfaces)
272
- port : int
273
- Port to listen on.
274
- """
275
- self.timeout = timeout if timeout is not None else 0.2
276
- self.server = TCPServerThread(
277
- interface=interface, port=port, timeout=self.timeout
278
- )
279
-
280
- @property
281
- def port(self) -> int:
282
- """Get the port.
283
-
284
- Raises
285
- ------
286
- RuntimeError
287
- If the server is not running
288
- """
289
- if self.server.factory is None:
290
- raise RuntimeError("server is not running")
291
- return self.server.port
292
-
293
- def start(self) -> None:
294
- """Start the server.
295
-
296
- Raises
297
- ------
298
- RuntimeError
299
- If the server is not running
300
- """
301
- if self.server is None:
302
- raise RuntimeError("server is not running")
303
- self.server.start()
304
-
305
- def stop(self) -> None:
306
- """Stop the server."""
307
- # pylint: disable=line-too-long
308
- self.server.reactor.callFromThread(self.server.reactor.stop) # type: ignore # noqa
309
- self.server.join()
310
-
311
-
312
- class TCPServer:
313
- """TCP Server."""
314
-
315
- _wrapper: Optional[ServerWrapper] = None
316
-
317
- def __init__(
318
- self,
319
- port: int,
320
- timeout: Optional[float] = None,
321
- interface: str = "",
322
- ) -> None:
323
- """Create a new server.
324
-
325
- Parameters
326
- ----------
327
- port : int
328
- Port to listen on.
329
- timeout : Optional[float]
330
- Timeout for the server.
331
- interface : str
332
- Interface to listen on. Defaults to '' (all interfaces)
333
- """
334
- self._port = port
335
- self._timeout = timeout
336
- self._interface = interface
337
- self._init_wrapper()
338
- self._running = False
339
-
340
- @property
341
- def port(self) -> int:
342
- """Get the port."""
343
- if self._wrapper is None:
344
- return self._port
345
- return self._wrapper.port
346
-
347
- def _init_wrapper(self) -> None:
348
- """Initialize the wrapper."""
349
- self._wrapper = ServerWrapper(
350
- port=self._port,
351
- timeout=self._timeout,
352
- interface=self._interface,
353
- )
354
-
355
- def start(self) -> None:
356
- """Start the server.
357
-
358
- Raises
359
- ------
360
- RuntimeError
361
- If the wrapper is not initialized
362
- """
363
- if self._running:
364
- return
365
- if not self._wrapper:
366
- self._init_wrapper()
367
- if not self._wrapper: # pragma: no cover (just for the linter)
368
- raise RuntimeError("Server wrapper is not initialized")
369
- self._wrapper.start()
370
- self._port = self._wrapper.port
371
- self._running = True
372
-
373
- def stop(self) -> None:
374
- """Stop the server."""
375
- if not self._running:
376
- return
377
- if not self._wrapper: # pragma: no cover (just for the linter)
378
- return
379
- self._wrapper.stop()
380
- self._running = False
381
- del self._wrapper
382
- self._wrapper = None
383
-
384
- def is_running(self) -> bool:
385
- """Check if the server is running.
386
-
387
- Returns
388
- -------
389
- bool
390
- True if the server is running, else False.
391
- """
392
- return self._running
393
-
394
- def __enter__(self) -> "TCPServer":
395
- """Enter the context manager."""
396
- self.start()
397
- return self
398
-
399
- def __exit__(
400
- self,
401
- exc_type: Optional[Type[BaseException]],
402
- exc_value: Optional[BaseException],
403
- traceback: Optional[TracebackType],
404
- ) -> None:
405
- """Exit the context manager."""
406
- self.stop()
407
-
408
- def restart(self) -> None:
409
- """Restart the server."""
410
- self.stop()
411
- self._init_wrapper()
412
- self.start()
@@ -1,180 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: waldiez
3
- Version: 0.1.6
4
- Summary: waldiez
5
- Project-URL: homepage, https://waldiez.github.io/py/
6
- Project-URL: repository, https://github.com/waldiez/py.git
7
- Author-email: Panagiotis Kasnesis <pkasnesis@thingenious.io>, Lazaros Toumanidis <laztoum@protonmail.com>
8
- License-File: LICENSE
9
- Classifier: Development Status :: 3 - Alpha
10
- Classifier: Intended Audience :: Developers
11
- Classifier: Intended Audience :: Science/Research
12
- Classifier: License :: OSI Approved :: MIT License
13
- Classifier: Operating System :: OS Independent
14
- Classifier: Programming Language :: Python
15
- Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Programming Language :: Python :: 3.12
19
- Requires-Python: <3.13,>=3.10
20
- Requires-Dist: autogen-agentchat==0.2.37
21
- Requires-Dist: jupytext
22
- Requires-Dist: twisted==24.10.0
23
- Provides-Extra: autogen-extras
24
- Requires-Dist: autogen-agentchat[anthropic]==0.2.37; extra == 'autogen-extras'
25
- Requires-Dist: autogen-agentchat[bedrock]==0.2.37; extra == 'autogen-extras'
26
- Requires-Dist: autogen-agentchat[gemini]==0.2.37; extra == 'autogen-extras'
27
- Requires-Dist: autogen-agentchat[groq]==0.2.37; extra == 'autogen-extras'
28
- Requires-Dist: autogen-agentchat[mistral]==0.2.37; extra == 'autogen-extras'
29
- Requires-Dist: autogen-agentchat[retrievechat-couchbase]==0.2.37; extra == 'autogen-extras'
30
- Requires-Dist: autogen-agentchat[retrievechat-mongodb]==0.2.37; extra == 'autogen-extras'
31
- Requires-Dist: autogen-agentchat[retrievechat-pgvector]==0.2.37; extra == 'autogen-extras'
32
- Requires-Dist: autogen-agentchat[retrievechat-qdrant]==0.2.37; extra == 'autogen-extras'
33
- Requires-Dist: autogen-agentchat[retrievechat]==0.2.37; extra == 'autogen-extras'
34
- Requires-Dist: autogen-agentchat[together]==0.2.37; extra == 'autogen-extras'
35
- Requires-Dist: autogen-agentchat[websurfer]==0.2.37; extra == 'autogen-extras'
36
- Requires-Dist: chromadb==0.5.15; extra == 'autogen-extras'
37
- Requires-Dist: fastembed==0.4.1; extra == 'autogen-extras'
38
- Requires-Dist: pgvector==0.3.5; extra == 'autogen-extras'
39
- Requires-Dist: psycopg[binary]>=3.2.3; extra == 'autogen-extras'
40
- Requires-Dist: pymongo==4.10.1; extra == 'autogen-extras'
41
- Requires-Dist: qdrant-client==1.12.0; extra == 'autogen-extras'
42
- Provides-Extra: dev
43
- Requires-Dist: autoflake==2.3.1; extra == 'dev'
44
- Requires-Dist: bandit==1.7.10; extra == 'dev'
45
- Requires-Dist: black[jupyter]==24.10.0; extra == 'dev'
46
- Requires-Dist: flake8==7.1.1; extra == 'dev'
47
- Requires-Dist: isort==5.13.2; extra == 'dev'
48
- Requires-Dist: mypy==1.13.0; extra == 'dev'
49
- Requires-Dist: pre-commit==4.0.1; extra == 'dev'
50
- Requires-Dist: pydocstyle==6.3.0; extra == 'dev'
51
- Requires-Dist: pylint==3.3.1; extra == 'dev'
52
- Requires-Dist: python-dotenv==1.0.1; extra == 'dev'
53
- Requires-Dist: ruff==0.7.1; extra == 'dev'
54
- Requires-Dist: types-pyyaml==6.0.12; extra == 'dev'
55
- Requires-Dist: yamllint==1.35.1; extra == 'dev'
56
- Provides-Extra: docs
57
- Requires-Dist: mdx-include==1.4.2; extra == 'docs'
58
- Requires-Dist: mdx-truly-sane-lists==1.3; extra == 'docs'
59
- Requires-Dist: mkdocs-jupyter==0.25.1; extra == 'docs'
60
- Requires-Dist: mkdocs-macros-plugin==1.3.6; extra == 'docs'
61
- Requires-Dist: mkdocs-material==9.5.42; extra == 'docs'
62
- Requires-Dist: mkdocs-minify-html-plugin==0.2.3; extra == 'docs'
63
- Requires-Dist: mkdocs==1.6.1; extra == 'docs'
64
- Requires-Dist: mkdocstrings-python==1.12.2; extra == 'docs'
65
- Requires-Dist: mkdocstrings[crystal,python]==0.26.2; extra == 'docs'
66
- Provides-Extra: test
67
- Requires-Dist: pytest-cov==5.0.0; extra == 'test'
68
- Requires-Dist: pytest-html==4.1.1; extra == 'test'
69
- Requires-Dist: pytest-sugar==1.0.0; extra == 'test'
70
- Requires-Dist: pytest-timeout==2.3.1; extra == 'test'
71
- Requires-Dist: pytest-xdist==3.6.1; extra == 'test'
72
- Requires-Dist: pytest==8.3.3; extra == 'test'
73
- Description-Content-Type: text/markdown
74
-
75
- # Waldiez
76
-
77
- ![CI Build](https://github.com/waldiez/py/actions/workflows/main.yaml/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/waldiez/py/badge.svg)](https://coveralls.io/github/waldiez/py) [![PyPI version](https://badge.fury.io/py/waldiez.svg)](https://badge.fury.io/py/waldiez)
78
-
79
- Translate a Waldiez flow:
80
-
81
- ![Flow](https://raw.githubusercontent.com/waldiez/py/refs/heads/main/docs/flow.png)
82
-
83
- To a python script or a jupyter notebook with the corresponding [autogen](https://github.com/microsoft/autogen/) agents and chats.
84
-
85
- ## Features
86
-
87
- - Export .waldiez flows to .py or .ipynb
88
- - Run a .waldiez flow
89
- - Include a `logs` folder with the logs of the flow in csv format
90
- - Provide a custom [IOSStream](https://autogen-ai.github.io/autogen/docs/reference/io/base#iostream) to handle input and output.
91
-
92
- ## Installation
93
-
94
- On PyPI:
95
-
96
- ```bash
97
- python -m pip install waldiez
98
- ```
99
-
100
- From the repository:
101
-
102
- ```bash
103
- python -m pip install git+https://github.com/waldiez/py.git
104
- ```
105
-
106
- ## Usage
107
-
108
- ### CLI
109
-
110
- ```bash
111
- # Export a Waldiez flow to a python script or a jupyter notebook
112
- waldiez --export /path/to/a/flow.waldiez --output /path/to/an/output[.py|.ipynb]
113
- # Export and run the script, optionally force generation if the output file already exists
114
- waldiez /path/to/a/flow.waldiez --output /path/to/an/output[.py] [--force]
115
- ```
116
-
117
- ### As a library
118
-
119
- #### Export a flow
120
-
121
- ```python
122
- # Export a Waldiez flow to a python script or a jupyter notebook
123
- from waldiez import WaldiezExporter
124
- flow_path = "/path/to/a/flow.waldiez"
125
- output_path = "/path/to/an/output.py" # or .ipynb
126
- exporter = WaldiezExporter.load(flow_path)
127
- exporter.export(output_path)
128
- ```
129
-
130
- #### Run a flow
131
-
132
- ```python
133
- # Run a flow
134
- from waldiez import WaldiezRunner
135
- flow_path = "/path/to/a/flow.waldiez"
136
- output_path = "/path/to/an/output.py"
137
- runner = WaldiezRunner.load(flow_path)
138
- runner.run(output_path=output_path)
139
- ```
140
-
141
- #### Run a flow with a custom IOStream
142
-
143
- ```python
144
- # Run the flow with a custom IOStream
145
- from waldiez import WaldiezIOStream, WaldiezRunner
146
-
147
- flow_path = "/path/to/a/flow.waldiez"
148
- output_path = "/path/to/an/output.py"
149
-
150
- def print_function(*values, **args) -> None:
151
- """A custom print function."""
152
- print(values)
153
-
154
- def on_prompt_input(prompt: str) -> str:
155
- """A custom input function."""
156
- return input(prompt)
157
-
158
- io_stream = WaldiezIOStream(
159
- print_function=print_function,
160
- on_prompt_input=on_prompt_input,
161
- input_timeout=30,
162
- )
163
- with WaldiezIOStream.set_default(io_stream):
164
- runner = WaldiezRunner.load(flow_path)
165
- runner.run(stream=io_stream, output_path=output_path)
166
-
167
- io_stream.close()
168
-
169
- ```
170
-
171
- ### Tools
172
-
173
- - [autogen](https://github.com/microsoft/autogen/)
174
- - [juptytext](https://github.com/mwouts/jupytext)
175
- - [twisted](https://github.com/twisted/twisted)
176
- - [pydantic](https://github.com/pydantic/pydantic)
177
-
178
- ## License
179
-
180
- This project is licensed under the MIT License - see the [LICENSE](https://github.com/waldiez/py/blob/main/LICENSE) file for details.