Создание лаунчера для скриптов
Всем привет! Я начинающий программист, прошу не судить строго
Пишу различные телеграм-боты на Python для собственных задач. В качестве сервера использую свой ненужный ноутбук (понимаю, что это не сервер, а просто машина, на которой запускаются скрипты). Заметил, что если запускать скрипты с ботами, а может быть, и не только с ботами, через командную строку или через PyCharm, то через какое-то время они отключаются.
Решил написать лаунчер для своих скриптов, и вы не представляете, насколько тяжелой задачей для меня это оказалось...
Потратил 10 часов на пару с чатом GPT и так и не смог сделать что-то годное. Функционал, который я бы хотел видеть в лаунчере:
- Возможность добавления скриптов в базу программы с помощью кнопки "Обзор" (лаунчер просто запоминает путь, чтобы запускать скрипты напрямую из своих папок, а не дублировать их в свою директорию).
- Возможность удаления скриптов из базы программы.
- Возможность присваивания имен добавленным скриптам.
- Возможность включения режима "нон-стоп" для выбранных скриптов (когда скрипт будет запущен навсегда - до отключения. Лаунчер должен будет периодически проверять статус фактического процесса скрипта и при его внезапном отключении по любой причине - перезапускать).
- Возможность выборочного запуска и остановки скриптов.
- Возможность запуска и остановки всех скриптов.
И самое главное - визуальный интерфейс, чтобы в будущем я мог запаковать эту программу в .exe файл и наслаждаться.
Опишу примерный сценарий использования: Я просыпаюсь, завершаю работу над долгожданным мега-функциональным телеграм-ботом Открываю свой лаунчер скриптов, нажимаю на кнопку "Обзор" и добавляю путь до главного .py файла в моем проекте телеграм-бота. Нажимаю "Запуск в режиме нон-стоп" и с спокойной совестью уезжаю на острова. Даже если процесс телеграм-бота остановится, лаунчер обнаружит это и перезапустит процесс.
Первое время на пару с чатом GPT я писал приятный код и наслаждался внедренным функционалом. Но потом столкнулся с первой проблемой - лаунчер запускал скрипты из своей директории. Как я понял это? В папке с лаунчером начали появляться .db файлы, связанные с ботом. А сам бот не видел никакой информации из этих .bd файлов, которые находились в его директории.
Быстро исправил, просто рассказав об этом чатику GPT После этого начал реализовывать остальной функционал. Наконец, готовьтесь... финальный результат:
import tkinter as tk
from tkinter import filedialog
import subprocess
import threading
import os
import time
import psutil
class ScriptRunnerApp:
def __init__(self, master):
self.master = master
self.master.title("Script Runner")
self.script_path = tk.StringVar()
self.create_widgets()
def create_widgets(self):
tk.Label(self.master, text="Выберите скрипт .py:").grid(row=0, column=0, padx=10, pady=10)
tk.Entry(self.master, textvariable=self.script_path, width=50).grid(row=0, column=1, padx=10, pady=10)
tk.Button(self.master, text="Обзор", command=self.browse_script).grid(row=0, column=2, padx=10, pady=10)
tk.Button(self.master, text="Запустить", command=self.run_script).grid(row=1, column=0, columnspan=3, pady=10)
# Запуск потока для мониторинга скрипта
self.monitor_thread = threading.Thread(target=self.monitor_script)
self.monitor_thread.daemon = True # Поток будет завершен, когда основная программа закроется
self.monitor_thread.start()
def browse_script(self):
script_path = filedialog.askopenfilename(filetypes=[("Python Scripts", "*.py")])
if script_path:
self.script_path.set(script_path)
def run_script(self):
script_path = self.script_path.get()
if script_path:
subprocess.Popen(["python", script_path], cwd=os.path.dirname(script_path))
else:
tk.messagebox.showinfo("Ошибка", "Выберите скрипт перед запуском!")
def monitor_script(self):
while True:
script_path = self.script_path.get()
if script_path:
# Проверка, запущен ли скрипт
script_running = any(p.name().lower() == 'python' and script_path in p.cmdline() for p in psutil.process_iter())
if not script_running:
# Если скрипт не запущен, перезапустить его
subprocess.Popen(["python", script_path], cwd=os.path.dirname(script_path))
time.sleep(60) # Подождать 1 минуту перед следующей проверкой
if __name__ == "__main__":
root = tk.Tk()
app = ScriptRunnerApp(root)
root.mainloop()
Пришел к выводу, что сложно реализовать весь функционал, о котором я мечтаю, и написал на пару с чатом GPT этот прекрасный код. Код позволяет запустить графический интерфейс, в котором я могу выбрать путь до файла .py и запустить его из своей директории. Лаунчер каждую минуту проверяет фактический статус кода и перезапускает его только в случае его отключения, а не дублирования. Но. Не все так гладко. Он должен каждую минуту проверять статус процесса скрипта и запускать его только в случае его остановки. Но он перезапускает его каждую минуту, создавая дубликаты кода. Так как это телеграм-бот, вижу следующее в консоли:
C:\Users\admin\Desktop\python\ScriptRunner\venv\Scripts\python.exe C:\Users\admin\Desktop\python\ScriptRunner\main.py
2024-01-23 11:52:10,661 (__init__.py:1147 MainThread) ERROR - TeleBot: "Threaded polling exception: A request to the Telegram API was unsuccessful. Error code: 409. Description: Conflict: terminated by other getUpdates request; make sure that only one bot instance is running"
2024-01-23 11:52:10,661 (__init__.py:1149 MainThread) ERROR - TeleBot: "Exception traceback:
Traceback (most recent call last):
File "C:\python\Lib\site-packages\telebot\__init__.py", line 1140, in __threaded_polling
polling_thread.raise_exceptions()
File "C:\python\Lib\site-packages\telebot\util.py", line 110, in raise_exceptions
raise self.exception_info
File "C:\python\Lib\site-packages\telebot\util.py", line 92, in run
task(*args, **kwargs)
File "C:\python\Lib\site-packages\telebot\__init__.py", line 661, in __retrieve_updates
updates = self.get_updates(offset=(self.last_update_id + 1),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\python\Lib\site-packages\telebot\__init__.py", line 633, in get_updates
json_updates = apihelper.get_updates(
^^^^^^^^^^^^^^^^^^^^^^
File "C:\python\Lib\site-packages\telebot\apihelper.py", line 321, in get_updates
return _make_request(token, method_url, params=payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\python\Lib\site-packages\telebot\apihelper.py", line 164, in _make_request
json_result = _check_result(method_name, result)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\python\Lib\site-packages\telebot\apihelper.py", line 191, in _check_result
raise ApiTelegramException(method_name, result, result_json)
telebot.apihelper.ApiTelegramException: A request to the Telegram API was unsuccessful. Error code: 409. Description: Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
"
2024-01-23 11:52:13,982 (__init__.py:1147 MainThread) ERROR - TeleBot: "Threaded polling exception: A request to the Telegram API was unsuccessful. Error code: 409. Description: Conflict: terminated by other getUpdates request; make sure that only one bot instance is running"
2024-01-23 11:52:13,983 (__init__.py:1149 MainThread) ERROR - TeleBot: "Exception traceback:
Traceback (most recent call last):
File "C:\python\Lib\site-packages\telebot\__init__.py", line 1140, in __threaded_polling
polling_thread.raise_exceptions()
File "C:\python\Lib\site-packages\telebot\util.py", line 110, in raise_exceptions
raise self.exception_info
File "C:\python\Lib\site-packages\telebot\util.py", line 92, in run
task(*args, **kwargs)
File "C:\python\Lib\site-packages\telebot\__init__.py", line 661, in __retrieve_updates
updates = self.get_updates(offset=(self.last_update_id + 1),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\python\Lib\site-packages\telebot\__init__.py", line 633, in get_updates
json_updates = apihelper.get_updates(
^^^^^^^^^^^^^^^^^^^^^^
File "C:\python\Lib\site-packages\telebot\apihelper.py", line 321, in get_updates
return _make_request(token, method_url, params=payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\python\Lib\site-packages\telebot\apihelper.py", line 164, in _make_request
json_result = _check_result(method_name, result)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\python\Lib\site-packages\telebot\apihelper.py", line 191, in _check_result
raise ApiTelegramException(method_name, result, result_json)
telebot.apihelper.ApiTelegramException: A request to the Telegram API was unsuccessful. Error code: 409. Description: Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
"
2024-01-23 11:52:14,298 (__init__.py:1147 MainThread) ERROR - TeleBot: "Threaded polling exception: A request to the Telegram API was unsuccessful. Error code: 409. Description: Conflict: terminated by other getUpdates request; make sure that only one bot instance is running"
2024-01-23 11:52:14,298 (__init__.py:1149 MainThread) ERROR - TeleBot: "Exception traceback:
Traceback (most recent call last):
File "C:\python\Lib\site-packages\telebot\__init__.py", line 1140, in __threaded_polling
polling_thread.raise_exceptions()
File "C:\python\Lib\site-packages\telebot\util.py", line 110, in raise_exceptions
raise self.exception_info
File "C:\python\Lib\site-packages\telebot\util.py", line 92, in run
task(*args, **kwargs)
File "C:\python\Lib\site-packages\telebot\__init__.py", line 661, in __retrieve_updates
updates = self.get_updates(offset=(self.last_update_id + 1),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\python\Lib\site-packages\telebot\__init__.py", line 633, in get_updates
json_updates = apihelper.get_updates(
^^^^^^^^^^^^^^^^^^^^^^
File "C:\python\Lib\site-packages\telebot\apihelper.py", line 321, in get_updates
return _make_request(token, method_url, params=payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\python\Lib\site-packages\telebot\apihelper.py", line 164, in _make_request
json_result = _check_result(method_name, result)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\python\Lib\site-packages\telebot\apihelper.py", line 191, in _check_result
raise ApiTelegramException(method_name, result, result_json)
telebot.apihelper.ApiTelegramException: A request to the Telegram API was unsuccessful. Error code: 409. Description: Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
"
Не знаю, как решить эту проблему. Перепробовал все варианты, обсудил с чатом GPT, объяснил, что программа создает дубликаты, но никак не могу решить эту проблему. Помогите, пожалуйста!
Ответы (1 шт):
Amgarak, спасибо тебе огромное! Сказал чату gpt, чтобы сделал проверку по PID и все заработало как часы. Запускаю скрипт и через каждую минуту не перезапускается (не создает новые процессы одного и того же скрипта). Отключаю скрипт через диспетчер задач (имитируй возможную проблему из-за которой он может отключиться) и он перезапускается после проверки. В коносоли никаких проблем с дублированием не вижу. 10 часов вчера мучался с этим кодом, а тут очень краткий и годный совет!
От всей души, спасибо!!!
Вот мой итоговый код, может кому пригодится:
import tkinter as tk
from tkinter import filedialog, messagebox
import subprocess
import threading
import os
import time
import psutil
class ScriptRunnerApp:
def __init__(self, master):
self.master = master
self.master.title("Script Runner")
self.script_path = tk.StringVar()
self.process_pid = None
self.create_widgets()
def create_widgets(self):
tk.Label(self.master, text="Выберите скрипт .py:").grid(row=0, column=0, padx=10, pady=10)
tk.Entry(self.master, textvariable=self.script_path, width=50).grid(row=0, column=1, padx=10, pady=10)
tk.Button(self.master, text="Обзор", command=self.browse_script).grid(row=0, column=2, padx=10, pady=10)
tk.Button(self.master, text="Запустить", command=self.run_script).grid(row=1, column=0, columnspan=3, pady=10)
# Запуск потока для мониторинга скрипта
self.monitor_thread = threading.Thread(target=self.monitor_script)
self.monitor_thread.daemon = True # Поток будет завершен, когда основная программа закроется
self.monitor_thread.start()
def browse_script(self):
script_path = filedialog.askopenfilename(filetypes=[("Python Scripts", "*.py")])
if script_path:
self.script_path.set(script_path)
def run_script(self):
script_path = self.script_path.get()
if script_path:
process = subprocess.Popen(["python", script_path], cwd=os.path.dirname(script_path))
self.process_pid = process.pid
else:
messagebox.showinfo("Ошибка", "Выберите скрипт перед запуском!")
def monitor_script(self):
while True:
script_path = self.script_path.get()
if script_path:
if self.process_pid:
try:
# Проверка, существует ли процесс с заданным PID
process = psutil.Process(self.process_pid)
script_running = any("python" in cmdline for cmdline in process.cmdline())
except psutil.NoSuchProcess:
script_running = False
if not script_running:
# Если процесс не существует, перезапустить его
self.run_script()
time.sleep(10) # Подождать 1 минуту перед следующей проверкой
if __name__ == "__main__":
root = tk.Tk()
app = ScriptRunnerApp(root)
root.mainloop()
Его можно доработать, добавив допустим индикатор активности скрипта, кнопку остановки скрипта и многое другое.