Skip to content

Commit

Permalink
v1
Browse files Browse the repository at this point in the history
  • Loading branch information
fred913 committed Aug 27, 2024
1 parent 403aea7 commit 8f22a74
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 35 deletions.
8 changes: 8 additions & 0 deletions FluentPython/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,20 @@ def list_versions(self) -> list[FluentPyVersion]:
try:
ver_config = VersionConfig.model_validate_json(
ver_config_file.read_text("utf-8"))

except json.JSONDecodeError:
logger.error(
f"Invalid JSON in {ver_config_file}; skipping")
corrupted = True
break

except FileNotFoundError:
logger.error(
f"Version directory {version_dir} is missing fluentpy.json; skipping"
)
corrupted = True
break

ver_name = ver_config.name
ver_interp = Path(ver_config.interpreter)
if not ver_interp.is_file():
Expand Down
69 changes: 45 additions & 24 deletions FluentPython/gui/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,56 @@
import os
import subprocess
import threading
import time
from datetime import datetime

from loguru import logger
from PySide6.QtCore import QSize, Qt, Signal
from PySide6.QtCore import QEvent, QSize, Qt, Signal
from PySide6.QtGui import QColor, QFont, QTextCursor
from PySide6.QtWidgets import QLabel, QPushButton, QTextEdit, QWidget
from qfluentwidgets import FluentIcon as FIF
from qfluentwidgets import PushButton
from qfluentwidgets import InfoBar, InfoBarPosition, PushButton


class ConsoleExecutionPage(QWidget):
terminalUpdated = Signal(str)

def __init__(self, cmd: list[str], parent=None):
def __init__(self, cmd: list[str], tipbar: str, parent=None):
super().__init__(parent)

self._parent = parent

self.setObjectName('PythonConsole')

self.text_edit = QTextEdit(self)
self.text_edit.setFont(QFont('Consolas', 12))
# self.text_edit.setStyleSheet("background-color: black; color: white;")
self.text_edit.setTextColor(QColor(255, 255, 255))
self.text_edit.setStyleSheet(
"QTextEdit { background-color: rgb(45, 45, 45); border-radius: 8px; padding: 6px; }"
)
self.text_edit.setReadOnly(True)

self.text_edit.setPlainText("[Runner] Not started yet")

self.status_label = QLabel("State: Idle...", self)
self.idle_text = "State: Ready, click 'Start' to run \"" + (
tipbar or "<program>").strip() + "\""
self.status_label = QLabel(self.idle_text, self)
self.status_label.setFont(QFont('MiSans', 14))
self.status_label.setAlignment(Qt.AlignmentFlag.AlignTop
| Qt.AlignmentFlag.AlignLeft)

self.button_start = PushButton(FIF.PLAY, "Start", self)
self.button_start.clicked.connect(self.start_program)

self.button_end = PushButton(FIF.PAUSE, "Stop ", self)
self.button_end = PushButton(FIF.PAUSE, "Stop", self)
self.button_end.clicked.connect(self.stop_program)

self.buttons = [
self.button_start,
self.button_end,
]
self.buttons = [self.button_start, self.button_end]
for button in self.buttons:
button.setFont(QFont('MiSans', 10))

self.reposition()

self.child = None
self.stopping = False

self.cmd = cmd

def updateStatus(self, status: str):
Expand Down Expand Up @@ -82,7 +79,6 @@ def resizeEvent(self, event):
self.reposition()

def start_program(self, event):
# run subprocess: python -m pipenv run python adaptive_pipeline.py {config_json}
self.child = subprocess.Popen(
self.cmd,
stderr=subprocess.STDOUT,
Expand All @@ -102,13 +98,12 @@ def _():

self.updateTerminal(line)

if self.stopping:
self.updateStatus("Stopping...")
else:
self.updateStatus("Running...")
if not self.stopping:
self.updateStatus("Running")

self.updateTerminal(
f"[Runner] Program stopped with code {self.child.returncode}")
self.updateStatus("Idle...")
self.updateStatus(self.idle_text)

self.child = None
self.stopping = False
Expand All @@ -117,10 +112,33 @@ def _():

def stop_program(self, event):
if self.child is not None:
if not self.stopping:
self.stopping = True
self.updateTerminal("Stopping program...")
self.child.terminate()
if self.stopping:
InfoBar.warning(title="请稍等片刻",
content="程序已经进入中止中的状态",
orient=Qt.Orientation.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP_RIGHT,
duration=1500,
parent=self.topLevelWidget())
return

self.stopping = True
self.updateStatus("Stopping...")
self.updateTerminal("Stopping program...")
InfoBar.info(
title='正在停止程序...',
content=
"有时 JupyterLab 会无法中止,您可以直接在左侧选择其他 Tab,开启新的 Session。(持续优化中)",
orient=Qt.Orientation.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP_RIGHT,
duration=1500,
parent=self.topLevelWidget())

self.child.terminate()
time.sleep(0.5)
self.child.kill()
self.child.wait()

def updateTerminal(self, text: str):
text = text.rstrip() + '\n'
Expand All @@ -129,7 +147,10 @@ def updateTerminal(self, text: str):
f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}] {text}")
self.text_edit.moveCursor(QTextCursor.MoveOperation.End)

logger.debug(text)
# logger.debug(text)

# Emit the terminalUpdated signal
self.terminalUpdated.emit(text)

def __del__(self):
if self.child is not None:
Expand Down
182 changes: 171 additions & 11 deletions FluentPython/gui/jupyter.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
import socket
import subprocess
from dataclasses import dataclass

from loguru import logger
from PySide6.QtCore import QEvent, QSize, Qt
from PySide6.QtWidgets import (QFrame, QHBoxLayout, QLabel, QLineEdit,
QListWidget, QPushButton, QSizePolicy,
QVBoxLayout, QWidget)
from PySide6.QtWidgets import (QApplication, QFrame, QHBoxLayout, QLabel,
QLineEdit, QListWidget, QPushButton,
QSizePolicy, QVBoxLayout, QWidget)
from qfluentwidgets import Action, BodyLabel, CommandBar
from qfluentwidgets import FluentIcon as FIF
from qfluentwidgets import (FluentWindow, InfoBar, InfoBarPosition, LineEdit,
ListWidget, MessageBoxBase, PushButton,
ListWidget, MessageBox, MessageBoxBase, PushButton,
SingleDirectionScrollArea, SubtitleLabel,
TitleLabel, VBoxLayout, setFont)

from FluentPython.core.config import CFG, FluentPyVersion
from FluentPython.gui.console import ConsoleExecutionPage


def select_first_unused_port_from(start_port: int):
p = start_port

while True:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.bind(('localhost', p))
return p
except OSError:
p += 1


class PageJupyter(QWidget):

def __init__(self, parent=None):
Expand Down Expand Up @@ -59,6 +72,8 @@ def __init__(self, parent=None):

self.reload_versions()

self.clipboard = QApplication.clipboard()

def reload_versions(self):
self.version_list.clear()

Expand Down Expand Up @@ -111,17 +126,65 @@ def on_selecting_version(self, current, previous):
jupyterLabBtn.clicked.connect(lambda: self.start_jupyter_lab(ver))
lo.addWidget(jupyterLabBtn)

colabBtn = PushButton("启动 Colab 本地运行时")
colabBtn.clicked.connect(lambda: self.start_colab(ver))
lo.addWidget(colabBtn)

def start_jupyter_lab(self, ver: FluentPyVersion):
cmd = [
ver.py_executable, "-m", "pip", "install", "jupyterlab", "--index",
"https://pypi.tuna.tsinghua.edu.cn/simple"
]
logger.debug(f"Running command: {cmd}")
subprocess.check_output(cmd)
# test if jupyterlab is installed
installed = False
try:
subprocess.check_output(
[ver.py_executable, "-m", "jupyterlab", "--version"])
logger.info("JupyterLab is already installed")
installed = True
except subprocess.CalledProcessError:
pass

if not installed:
w = MessageBox("警告", "JupyterLab 未安装,是否现在安装?(确认后请等候一下,完成后对话框自动关闭)",
self.topLevelWidget())

if w.exec():
InfoBar.info(title='安装中',
content="正在安装 JupyterLab,请等待...",
orient=Qt.Orientation.Horizontal,
isClosable=True,
position=InfoBarPosition.BOTTOM_LEFT,
duration=100,
parent=self.topLevelWidget())

cmd = [
ver.py_executable, "-m", "pip", "install", "jupyterlab",
"--index", "https://pypi.tuna.tsinghua.edu.cn/simple"
]
logger.debug(f"Running command: {cmd}")
subprocess.check_output(cmd)

InfoBar.success(title='安装成功',
content="JupyterLab 安装成功,将启动 Jupyter Lab...",
orient=Qt.Orientation.Horizontal,
isClosable=True,
position=InfoBarPosition.BOTTOM_LEFT,
duration=1500,
parent=self.topLevelWidget())
else:
InfoBar.info(title='已取消',
content="已取消安装,请手动安装 JupyterLab 后再试。",
orient=Qt.Orientation.Horizontal,
isClosable=True,
position=InfoBarPosition.BOTTOM_LEFT,
duration=2000,
parent=self.topLevelWidget())
return

cmd = [ver.py_executable, "-m", "jupyter", "lab"]
logger.debug(f"Running command: {cmd}")
win = ConsoleExecutionPage(cmd, parent=self.topLevelWidget())

win = ConsoleExecutionPage(
cmd,
tipbar=f"JupyterLab[{ver.name}, {'.'.join(map(str, ver.version))}]",
parent=self.topLevelWidget())

win.setObjectName("JupyterLab-tmp123")

Expand All @@ -137,3 +200,100 @@ def cleanup():
tlw.navigationInterface.removeWidget(win.objectName())

tlw.stackedWidget.currentChanged.connect(lambda: cleanup())

def start_colab(self, ver: FluentPyVersion):
# test if jupyterlab is installed
installed = False
try:
subprocess.check_output(
[ver.py_executable, "-m", "jupyterlab", "--version"])
logger.info("JupyterLab is already installed")
installed = True
except subprocess.CalledProcessError:
pass

if not installed:
w = MessageBox("警告", "JupyterLab 未安装,是否现在安装?(确认后请等候一下,完成后对话框自动关闭)",
self.topLevelWidget())

if w.exec():
InfoBar.info(title='安装中',
content="正在安装 JupyterLab,请等待...",
orient=Qt.Orientation.Horizontal,
isClosable=True,
position=InfoBarPosition.BOTTOM_LEFT,
duration=100,
parent=self.topLevelWidget())

cmd = [
ver.py_executable, "-m", "pip", "install", "jupyterlab",
"--index", "https://pypi.tuna.tsinghua.edu.cn/simple"
]
logger.debug(f"Running command: {cmd}")
subprocess.check_output(cmd)

InfoBar.success(title='安装成功',
content="JupyterLab 安装成功",
orient=Qt.Orientation.Horizontal,
isClosable=True,
position=InfoBarPosition.BOTTOM_LEFT,
duration=1500,
parent=self.topLevelWidget())
else:
InfoBar.info(title='已取消',
content="已取消安装,请手动安装 JupyterLab 后再试。",
orient=Qt.Orientation.Horizontal,
isClosable=True,
position=InfoBarPosition.BOTTOM_LEFT,
duration=2000,
parent=self.topLevelWidget())
return

port = select_first_unused_port_from(8888)

cmd = [
ver.py_executable, "-m", "jupyter", "notebook",
"--NotebookApp.allow_origin='https://colab.research.google.com'",
"--port=8888", "--no-browser"
]
logger.debug(f"Running command: {cmd}")

win = ConsoleExecutionPage(
cmd,
tipbar=f"ColabRt[{ver.name}, {'.'.join(map(str, ver.version))}]",
parent=self.topLevelWidget())

win.setObjectName("ColabRt-tmp123")

tlw = self.topLevelWidget()
assert isinstance(tlw, FluentWindow), "Invalid top level widget"
tlw.addSubInterface(win, FIF.CODE, "[TMP] Colab Local Runtime")
tlw.switchTo(win)

tlw.stackedWidget.currentChanged.connect(lambda: cleanup())

win.updateTerminal("【提示】启动后,复制连接地址到 Google Colab 即可连接本地运行时。")
win.updateTerminal("【提示 2.0】运行时就绪后会出现一条通知,自动复制连接地址。提示出现后放心粘贴即可。")

def _termhandler(data: str):
logger.debug(f"Terminal output: {data}".strip())
if data.startswith("http://localhost:") and "?token=" in data:
data = data.replace("/tree", "/")
data = data.replace("/lab", "/")
self.clipboard.setText(data)
InfoBar.success(title='启动成功',
content="连接成功,连接地址已复制到剪贴板。",
orient=Qt.Orientation.Horizontal,
isClosable=True,
position=InfoBarPosition.BOTTOM_LEFT,
duration=1500,
parent=self.topLevelWidget())
win.updateTerminal("【提示】连接成功,连接地址已复制到剪贴板。")

win.terminalUpdated.connect(_termhandler)

def cleanup():
win.stop_program(None)

tlw.stackedWidget.view.removeWidget(win)
tlw.navigationInterface.removeWidget(win.objectName())

0 comments on commit 8f22a74

Please sign in to comment.