0

What is the correct way of communicating (passing strings) between two different python scripts?

I have a ui.py script which utilizes PySide6 to generate a GUI, and I have another bot.py script which listens discord/telegram conversations and catching some keywords using async functions. Both scripts are at the same directory.

I have put Asyncio event loop code in my bot.py file to a function named runscript(), and using multiprocessing.Process from ui.py I run that function after I click a PySide6 QPushButton.

So the issue here is I want to display that keywords bot.py catches in my GUI so I need to pass that strings to ui.py (there will be need of passing strings the other way -from ui.py to bot.py- in the future) but I don't know how to this. I have tried multiprocessing.Pipe but that blocks my code because script fetches messages from discord/telegram when a new message arrives (using Asyncio) and I can not wait that to happen.

#bot.py

# do other stuff above here
@discord_client.event
async def on_message(message):
    if message.channel.id in discord_channel_list:
        discord_message = message.content
        selected_symbol = message_analyzer(discord_message)
        print(selected_symbol)

async def discord_connection():
    await discord_client.start(discord_token)

def runscript():
    connection = asyncio.get_event_loop()
    connection.create_task(binance_connection())
    connection.create_task(discord_connection())
    connection.create_task(telegram_connection())
    connection.create_task(connection_check())

    try:
        connection.run_forever()
    except KeyboardInterrupt:
        print("\nShutting down...")
    except:
        print("\nWARN! Shutting down...")

For example I need to get value of selected_symbol and transfer it to the ui.py

#ui.py

import bot

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.start_button = QPushButton("Start")
        self.start_button.clicked.connect(self.run)

    def run(self):
        bot_process = Process(target=bot.runscript)
        bot_process.daemon = True
        bot_process.start()

What is the correct way to achieve this? Thanks in advance.

2
  • Would using a relational database (like sqlite) work? Commented Mar 3, 2021 at 23:12
  • I am not sure. I ended up merging two codes into a single .py file and using qasync. Commented Mar 5, 2021 at 14:28

1 Answer 1

1

In general Qt is not process-safe so you should not update the GUI from another process. An alternative is to create a QThread (or threading.Thread) that only parses the information of the Queue and emits a signal with the information to update the GUI. Another option is to use a QTimer that does the above: monitor the Queue.

class Worker(QObject):
    messageChanged = Signal(str)

def monitoring(p, worker):
    while True:
        try:
            msg = p.recv()
        except EOFError:
            break
        else:
            worker.messageChanged.emit(msg)
r, w = Pipe(duplex=False)
p = Process(target=foo, args=(w,))

worker = Worker()
worker.messageChanged.connect(self.some_slot)

threading.Thread(target=monitoring, args=(r, worker), daemon=True).start()
p.start()

But using multiprocessing can add unnecessary complexity, instead you can use qasync(python -m pip install qasync) and then use asyncio:

import asyncio
from functools import cached_property
import sys

import discord
from PySide6 import QtCore, QtWidgets
from qasync import QEventLoop, asyncSlot


class DiscordManager(QtCore.QObject):
    connected = QtCore.Signal()
    disconnected = QtCore.Signal()
    messageChanged = QtCore.Signal(discord.message.Message)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.client.event(self.on_message)
        self.client.event(self.on_connect)
        self.client.event(self.on_disconnect)

    @cached_property
    def client(self):
        return discord.Client()

    async def on_message(self, message):
        self.messageChanged.emit(message)

    async def start(self):
        await self.client.start(
            "<TOKEN>"
        )

    async def close(self):
        await self.client.close()

    async def on_connect(self):
        self.connected.emit()

    async def on_disconnect(self):
        self.disconnected.emit()


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.button = QtWidgets.QPushButton("Start")
        self.label = QtWidgets.QLabel(alignment=QtCore.Qt.AlignCenter)

        central_widget = QtWidgets.QWidget()
        lay = QtWidgets.QVBoxLayout(central_widget)
        lay.addWidget(self.button)
        lay.addWidget(self.label)
        self.setCentralWidget(central_widget)

        self.button.clicked.connect(self.handle_clicked)

        self.manager.connected.connect(self.handle_connected)
        self.manager.disconnected.connect(self.handle_disconnected)
        self.manager.messageChanged.connect(self.handle_message)

    @cached_property
    def manager(self):
        return DiscordManager()

    @asyncSlot()
    async def handle_clicked(self):
        if self.button.text() == "Start":
            await self.manager.start()
        else:
            await self.manager.close()

    def handle_message(self, message):
        self.label.setText(message.content)

    def handle_connected(self):
        self.button.setText("Stop")

    def handle_disconnected(self):
        self.button.setText("Start")


def main():
    app = QtWidgets.QApplication(sys.argv)
    loop = QEventLoop(app)
    asyncio.set_event_loop(loop)
    w = MainWindow()
    w.show()
    loop.run_forever()


if __name__ == "__main__":
    main()
Sign up to request clarification or add additional context in comments.

7 Comments

Tried this now and works quite well, however I need to start() discord.Client when I press a button on the GUI, not when script initializes. Is it possible to do such thing? If it is this is a good start me. After this I need to do same thing for telethon and implement other methods like message analyzer.
@AlperenÖztürk Update the example with that functionality
@AlperenÖztürk That is another problem so you have to create another post. If you do not know the rules then read How to Ask and check the tour, here we solve specific problems, we do not guide anyone in their project as it is too much task nor do we implement what is not in the question. We won't do all your work
Of course, I don't have any purpose like that. Sorry for my lack of knowledge. I am just new to stackoverflow and how this community works. Deleted the comment.
@AlperenÖztürk Precisely to avoid having that type of discussion or making that kind of excuses I have provided you with those links that I hope you read before publishing a new post. Also if you can read How to Answer
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.