From 5d35ed84583c3f9cf062a4a6487a67de841e8ef6 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Sat, 9 Sep 2023 14:51:38 +0200 Subject: [PATCH 01/14] Test application to run platon --- finalcif/tools/process.py | 80 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 finalcif/tools/process.py diff --git a/finalcif/tools/process.py b/finalcif/tools/process.py new file mode 100644 index 00000000..9cf6a9b0 --- /dev/null +++ b/finalcif/tools/process.py @@ -0,0 +1,80 @@ +import sys, os +import time +import threading +from pathlib import Path + +from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QTextEdit, QVBoxLayout, QWidget, QLabel +from PyQt5.QtCore import QProcess, QIODevice, QTimer, QTime + + +class ProcessWidget(QWidget): + def __init__(self): + super().__init__() + layout = QVBoxLayout() + + self.text_widget = QTextEdit() + layout.addWidget(self.text_widget) + + self.button = QPushButton("Run QProcess") + layout.addWidget(self.button) + + self.time_label = QLabel() + layout.addWidget(self.time_label) + + self.setLayout(layout) + + self.button.clicked.connect(self.run_process) + self.process = None + self.stop_monitor = False + + self.timer = QTimer(self) + self.timer.timeout.connect(self.update_time) + self.timer.start(1000) + + def update_time(self): + current_time = QTime.currentTime() + time_text = current_time.toString("hh:mm:ss") + self.time_label.setText(f"Current Time: {time_text}") + + def run_process(self): + self.text_widget.clear() + self.process = QProcess() + self.process.readyReadStandardOutput.connect(self.on_ready_read) + Path("tests/examples/work/cu_BruecknerJK_153F40_0m.chk").unlink(missing_ok=True) + self.process.start("platon", ["-U", + "tests/examples/work/cu_BruecknerJK_153F40_0m.cif"]) + threading.Thread(target=self.monitor_output_log).start() + + def on_ready_read(self): + output = self.process.readAllStandardOutput().data().decode() + self.text_widget.append(output) + + def monitor_output_log(self): + while not self.stop_monitor: + try: + with open("tests/examples/work/cu_BruecknerJK_153F40_0m.chk", "r") as log_file: + content = log_file.read() + if '# *' in content: + self.stop_program() + except FileNotFoundError: + pass + time.sleep(1) + + def stop_program(self): + print('# Stopping Platon!') + self.stop_monitor = True + if self.process and self.process.state() == QProcess.Running: + self.process.terminate() + #app.quit() + + +app = QApplication(sys.argv) +window = QMainWindow() +window.setWindowTitle("QProcess Example") + +process_widget = ProcessWidget() +window.setCentralWidget(process_widget) + +window.show() + +sys.exit(app.exec_()) From b0864ee2712ecd36a5277d35145268abdeb909af Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Sat, 9 Sep 2023 22:36:10 +0200 Subject: [PATCH 02/14] Using simpler solution for platon run --- finalcif/appwindow.py | 104 +++++----------------- finalcif/tools/process.py | 183 +++++++++++++++++++++++++++----------- 2 files changed, 151 insertions(+), 136 deletions(-) diff --git a/finalcif/appwindow.py b/finalcif/appwindow.py index 90aa8eea..d1a9de01 100644 --- a/finalcif/appwindow.py +++ b/finalcif/appwindow.py @@ -58,6 +58,7 @@ strip_finalcif_of_name from finalcif.tools.options import Options from finalcif.tools.platon import Platon +from finalcif.tools.process import PlatonRunner from finalcif.tools.settings import FinalCifSettings from finalcif.tools.shred import ShredCIF from finalcif.tools.space_groups import SpaceGroups @@ -990,99 +991,34 @@ def do_pdf_checkcif(self) -> None: except (FileNotFoundError, PermissionError): pass - def append_to_ciflog_without_newline(self, text: str = '') -> None: - self.ui.CheckCifLogPlainTextEdit.moveCursor(QtGui.QTextCursor.End) - self.ui.CheckCifLogPlainTextEdit.insertPlainText(text) - self.ui.CheckCifLogPlainTextEdit.moveCursor(QtGui.QTextCursor.End) - def do_offline_checkcif(self) -> None: - """ - Performs a checkcif with platon and displays it in the text editor of the MainStackedWidget. - """ - current_block = self.ui.datanameComboBox.currentIndex() + self.ui.CheckcifButton.setDisabled(True) + app.processEvents() self.ui.CheckCifLogPlainTextEdit.clear() self.ui.MainStackedWidget.go_to_checkcif_page() self.ui.CheckCIFResultsTabWidget.setCurrentIndex(0) + self.ui.CheckcifPlaintextEdit.clear() self.ui.CheckCifLogPlainTextEdit.appendPlainText("Running Checkcif locally. Please wait...\n") - QApplication.processEvents() - # makes sure also the currently edited item is saved: self.ui.cif_main_table.setCurrentItem(None) - self.ui.CheckcifPlaintextEdit.clear() if not self.save_current_cif_file(): self.ui.CheckCifLogPlainTextEdit.appendPlainText('Unable to save CIF file. Aborting action...') return None - self.load_cif_file(self.cif.finalcif_file, block=current_block, load_changes=False) + self.load_cif_file(self.cif.finalcif_file, block=self.ui.datanameComboBox.currentIndex(), load_changes=False) self.ui.MainStackedWidget.go_to_checkcif_page() - QApplication.processEvents() - timeout = 350 - try: - p = Platon(self.cif.fileobj.resolve(), timeout, '-u') - except Exception as e: - print(e) - return - checkcif_out = self.ui.CheckcifPlaintextEdit - checkcif_out.setPlainText('Platon output: \nThis might not be the same as the IUCr CheckCIF!') - QApplication.processEvents() - p.start() - if not self.wait_for_chk_file(p): - checkcif_out.appendPlainText('Platon did not start. No .chk file from Platon found!') - checkcif_out.appendPlainText(p.platon_output) - p.kill() - p.delete_orphaned_files() - return - checkcif_out.appendPlainText('\n' + '#' * 80) - checkcif_out.setLineWrapMode(QPlainTextEdit.NoWrap) - self.wait_until_platon_finished(timeout) - checkcif_out.appendPlainText(p.platon_output) - p.parse_chk_file() - vrf_txt = '' - with suppress(FileNotFoundError): - vrf_txt = self.cif.finalcif_file.with_suffix('.vrf').read_text() - if p.chk_file_text: - with suppress(AttributeError): - checkcif_out.appendPlainText(p.chk_file_text) - checkcif_out.appendPlainText('\n' + '#' * 27 + ' Validation Response Forms ' + '#' * 26 + '\n') - checkcif_out.appendPlainText(vrf_txt) - checkcif_out.verticalScrollBar().setValue(0) - moiety = self.ui.cif_main_table.getTextFromKey(key='_chemical_formula_moiety', col=0) - if p.formula_moiety and moiety in ['', '?'] and not self.cif.is_multi_cif: - self.ui.cif_main_table.setText(key='_chemical_formula_moiety', txt=p.formula_moiety, column=Column.EDIT) - print('Killing platon!') - p.kill() - p.delete_orphaned_files() - - def wait_for_chk_file(self, p) -> bool: - time.sleep(2) - stop = 0 - while not p.chkfile.exists(): - self.append_to_ciflog_without_newline('.') - QApplication.processEvents() - time.sleep(2) - if not p.plat: - self.append_to_ciflog_without_newline('aborted here') - self.append_to_ciflog_without_newline(p.platon_output) - return False - stop += 1 - if stop == 8: - return False - return True + runner = PlatonRunner(parent=self, + log_widget=self.ui.CheckCifLogPlainTextEdit, + output_widget=self.ui.CheckcifPlaintextEdit, + cif_file=self.cif.fileobj) + runner.tick.connect(self.append_to_ciflog_without_newline) + runner.run_process() + runner.finished.connect(lambda: self.ui.CheckcifButton.setEnabled(True)) + app.processEvents() - def wait_until_platon_finished(self, timeout: int = 300): - stop = 0 - if not self.cif.finalcif_file.with_suffix('.chk').exists(): - self.ui.CheckCifLogPlainTextEdit.appendPlainText('Platon returned no output.') - QApplication.processEvents() - return - with suppress(FileNotFoundError): - while self.cif.finalcif_file.with_suffix('.chk').stat().st_size < 200: - self.append_to_ciflog_without_newline('*') - QApplication.processEvents() - if stop == timeout: - self.ui.CheckcifPlaintextEdit.appendPlainText('PLATON timed out') - break - time.sleep(1) - stop += 1 - time.sleep(0.5) + def append_to_ciflog_without_newline(self, text: str = '') -> None: + self.ui.CheckCifLogPlainTextEdit.moveCursor(QtGui.QTextCursor.End) + self.ui.CheckCifLogPlainTextEdit.insertPlainText(text) + self.ui.CheckCifLogPlainTextEdit.moveCursor(QtGui.QTextCursor.End) + self.ui.CheckCifLogPlainTextEdit.unsetCursor() def set_checkcif_output_font(self, ccpe: 'QPlainTextEdit') -> None: doc = ccpe.document() @@ -1540,9 +1476,9 @@ def _load_block(self, index: int, load_changes: bool = True) -> None: except Exception as e: not_ok = e print(e) - #if DEBUG: + # if DEBUG: raise - #unable_to_open_message(Path(self.cif.filename), not_ok) + # unable_to_open_message(Path(self.cif.filename), not_ok) self.load_recent_cifs_list() if self.options.track_changes and load_changes: changes_exist = False diff --git a/finalcif/tools/process.py b/finalcif/tools/process.py index 9cf6a9b0..690cdcde 100644 --- a/finalcif/tools/process.py +++ b/finalcif/tools/process.py @@ -1,32 +1,139 @@ -import sys, os -import time +import os +import sys import threading +import time from pathlib import Path -from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QTextEdit, QVBoxLayout, QWidget, QLabel -from PyQt5.QtCore import QProcess, QIODevice, QTimer, QTime +from PyQt5 import QtCore +from PyQt5.QtCore import QProcess, QTimer, QTime +from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QLabel, \ + QPlainTextEdit + + +class PlatonRunner(QtCore.QObject): + finished = QtCore.pyqtSignal(bool) + tick = QtCore.pyqtSignal(str) + + def __init__(self, parent, output_widget: QPlainTextEdit, log_widget: QPlainTextEdit, cif_file: Path): + super().__init__(parent) + self.cif_file = cif_file.resolve().absolute() + self.process = None + self.is_stopped = False + self._origdir = None + self.output_widget = output_widget + self.log_widget = log_widget + self.formula_moiety = '' + self.Z = '' + self.chk_file_text = '' + + def run_process(self): + self._origdir = os.curdir + os.chdir(self.cif_file.parent) + self.formula_moiety = '' + self.Z = '' + self.process = QProcess() + self.output_widget.clear() + threading.Thread(target=self._monitor_output_log).start() + # self.process.readyReadStandardOutput.connect(self.on_ready_read) + self.process.finished.connect(self._onfinished) + self.cif_file.with_suffix('.chk').unlink(missing_ok=True) + self.process.start(self.platon_exe, ["-U", str(self.cif_file.name)]) + + def _onfinished(self): + self._on_ready_read() + os.chdir(self._origdir) + self._parse_chk_file() + self.output_widget.setPlainText(self.chk_file_text) + self.finished.emit(True) + self.delete_orphaned_files() + + def _on_ready_read(self): + output = self.process.readAllStandardOutput().data().decode() + self.log_widget.setPlainText(output) + + def _monitor_output_log(self): + while not self.is_stopped: + self.tick.emit('#') + time.sleep(1) + try: + log_file = self.cif_file.with_suffix('.chk').read_text('latin1', errors='ignore') + if 'Unresolved or to be Checked Issue' in log_file: + self._stop_program() + except FileNotFoundError: + break + + def _stop_program(self): + self.is_stopped = True + if self.process and self.process.state() == QProcess.Running: + self.process.terminate() + self.finished.emit(True) + + def _parse_chk_file(self): + try: + self.chk_file_text = self.cif_file.with_suffix('.chk').read_text(encoding='latin1', errors='ignore') + except FileNotFoundError as e: + print('CHK file not found:', e) + self.chk_file_text = '' + for num, line in enumerate(self.chk_file_text.splitlines(keepends=False)): + if line.startswith('# MoietyFormula'): + self.formula_moiety = ' '.join(line.split(' ')[2:]) + if line.startswith('# Z'): + self.Z = line[19:24].strip(' ') + + @property + def platon_exe(self): + if sys.platform.startswith('win'): + in_pwt = r'C:\pwt\platon.exe' + else: + in_pwt = 'platon' + if Path(in_pwt).exists(): + return in_pwt + else: + return 'platon' + + def kill(self): + if sys.platform.startswith('win'): + with suppress(FileNotFoundError): + subprocess.run(["taskkill", "/f", "/im", "platon.exe"], shell=False) + if sys.platform[:5] in ('linux', 'darwi'): + with suppress(FileNotFoundError): + subprocess.run(["killall", "platon"], shell=False) + + def delete_orphaned_files(self): + # delete orphaned files: + for ext in ['.ckf', '.fcf', '.def', '.lis', '.sar', '.ckf', + '.sum', '.hkp', '.pjn', '.bin', '.spf']: + try: + file = self.cif_file.resolve().with_suffix(ext) + if file.stat().st_size < 100: + file.unlink(missing_ok=True) + if file.suffix in ['.sar', '.spf', '.ckf']: + file.unlink(missing_ok=True) + except FileNotFoundError: + pass class ProcessWidget(QWidget): def __init__(self): super().__init__() layout = QVBoxLayout() - - self.text_widget = QTextEdit() + self.text_widget = QPlainTextEdit() + self.log_widget = QPlainTextEdit() + layout.addWidget(self.log_widget) layout.addWidget(self.text_widget) - self.button = QPushButton("Run QProcess") layout.addWidget(self.button) - self.time_label = QLabel() layout.addWidget(self.time_label) - self.setLayout(layout) - - self.button.clicked.connect(self.run_process) - self.process = None - self.stop_monitor = False - + self.runner = PlatonRunner(parent=self, output_widget=self.text_widget, log_widget=self.log_widget, + cif_file=Path("tests/examples/work/cu_BruecknerJK_153F40_0m.cif")) + self.button.clicked.connect(lambda x: self.button.setDisabled(True)) + self.button.clicked.connect(lambda x: self.runner.run_process()) + self.button.clicked.connect(lambda x: self.log_widget.setPlainText('Running Platon')) + self.runner.finished.connect(lambda x: self.button.setEnabled(True)) + + # Only to show that the main thread works continuously: self.timer = QTimer(self) self.timer.timeout.connect(self.update_time) self.timer.start(1000) @@ -36,45 +143,17 @@ def update_time(self): time_text = current_time.toString("hh:mm:ss") self.time_label.setText(f"Current Time: {time_text}") - def run_process(self): - self.text_widget.clear() - self.process = QProcess() - self.process.readyReadStandardOutput.connect(self.on_ready_read) - Path("tests/examples/work/cu_BruecknerJK_153F40_0m.chk").unlink(missing_ok=True) - self.process.start("platon", ["-U", - "tests/examples/work/cu_BruecknerJK_153F40_0m.cif"]) - threading.Thread(target=self.monitor_output_log).start() - - def on_ready_read(self): - output = self.process.readAllStandardOutput().data().decode() - self.text_widget.append(output) - - def monitor_output_log(self): - while not self.stop_monitor: - try: - with open("tests/examples/work/cu_BruecknerJK_153F40_0m.chk", "r") as log_file: - content = log_file.read() - if '# *' in content: - self.stop_program() - except FileNotFoundError: - pass - time.sleep(1) - - def stop_program(self): - print('# Stopping Platon!') - self.stop_monitor = True - if self.process and self.process.state() == QProcess.Running: - self.process.terminate() - #app.quit() - -app = QApplication(sys.argv) -window = QMainWindow() -window.setWindowTitle("QProcess Example") +if __name__ == '__main__': + app = QApplication(sys.argv) + window = QMainWindow() + window.setWindowTitle("QProcess Example") + window.setMinimumWidth(800) + window.setMinimumHeight(600) -process_widget = ProcessWidget() -window.setCentralWidget(process_widget) + process_widget = ProcessWidget() + window.setCentralWidget(process_widget) -window.show() + window.show() -sys.exit(app.exec_()) + sys.exit(app.exec_()) From f0a2afcb895f3bede26a60bec5b83fee1714b2c2 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Sat, 9 Sep 2023 22:50:04 +0200 Subject: [PATCH 03/14] Adds missing import --- finalcif/tools/process.py | 1 + 1 file changed, 1 insertion(+) diff --git a/finalcif/tools/process.py b/finalcif/tools/process.py index 690cdcde..e3b0c837 100644 --- a/finalcif/tools/process.py +++ b/finalcif/tools/process.py @@ -1,4 +1,5 @@ import os +import subprocess import sys import threading import time From f155fba5593f4d0ee1c60d690d0d3da769ad822a Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Sat, 9 Sep 2023 22:54:51 +0200 Subject: [PATCH 04/14] Add missing import and warn for missing platon --- finalcif/appwindow.py | 3 +++ finalcif/tools/process.py | 1 + 2 files changed, 4 insertions(+) diff --git a/finalcif/appwindow.py b/finalcif/appwindow.py index d1a9de01..6209b83d 100644 --- a/finalcif/appwindow.py +++ b/finalcif/appwindow.py @@ -1009,6 +1009,9 @@ def do_offline_checkcif(self) -> None: log_widget=self.ui.CheckCifLogPlainTextEdit, output_widget=self.ui.CheckcifPlaintextEdit, cif_file=self.cif.fileobj) + if not Path(runner.platon_exe).resolve().exists(): + self.ui.CheckCifLogPlainTextEdit.setPlainText('Platon executable not found!') + self.ui.CheckCifLogPlainTextEdit.setPlainText('You can download Platon at http://www.platonsoft.nl/platon/') runner.tick.connect(self.append_to_ciflog_without_newline) runner.run_process() runner.finished.connect(lambda: self.ui.CheckcifButton.setEnabled(True)) diff --git a/finalcif/tools/process.py b/finalcif/tools/process.py index e3b0c837..bb011998 100644 --- a/finalcif/tools/process.py +++ b/finalcif/tools/process.py @@ -3,6 +3,7 @@ import sys import threading import time +from contextlib import suppress from pathlib import Path from PyQt5 import QtCore From a438500d248514137b439f7306b2034e70ff7b2c Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Sat, 9 Sep 2023 22:57:07 +0200 Subject: [PATCH 05/14] Update appwindow.py --- finalcif/appwindow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/finalcif/appwindow.py b/finalcif/appwindow.py index 6209b83d..d49120a0 100644 --- a/finalcif/appwindow.py +++ b/finalcif/appwindow.py @@ -1010,8 +1010,8 @@ def do_offline_checkcif(self) -> None: output_widget=self.ui.CheckcifPlaintextEdit, cif_file=self.cif.fileobj) if not Path(runner.platon_exe).resolve().exists(): - self.ui.CheckCifLogPlainTextEdit.setPlainText('Platon executable not found!') - self.ui.CheckCifLogPlainTextEdit.setPlainText('You can download Platon at http://www.platonsoft.nl/platon/') + self.ui.CheckCifLogPlainTextEdit.setPlainText('\nPlaton executable not found!') + self.ui.CheckCifLogPlainTextEdit.appendPlainText('You can download Platon at http://www.platonsoft.nl/platon/\n') runner.tick.connect(self.append_to_ciflog_without_newline) runner.run_process() runner.finished.connect(lambda: self.ui.CheckcifButton.setEnabled(True)) From 7286f7a70f747a5aa0f9e724685437aa141db1e7 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Sun, 10 Sep 2023 07:46:22 +0200 Subject: [PATCH 06/14] Do not do chdir --- finalcif/appwindow.py | 3 ++- finalcif/tools/process.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/finalcif/appwindow.py b/finalcif/appwindow.py index d49120a0..2b16e252 100644 --- a/finalcif/appwindow.py +++ b/finalcif/appwindow.py @@ -12,6 +12,7 @@ from datetime import datetime from math import sin, radians from pathlib import Path, WindowsPath +from shutil import which from typing import Union, Dict, Tuple, List, Optional import gemmi.cif @@ -1009,7 +1010,7 @@ def do_offline_checkcif(self) -> None: log_widget=self.ui.CheckCifLogPlainTextEdit, output_widget=self.ui.CheckcifPlaintextEdit, cif_file=self.cif.fileobj) - if not Path(runner.platon_exe).resolve().exists(): + if not Path(which(runner.platon_exe)).exists(): self.ui.CheckCifLogPlainTextEdit.setPlainText('\nPlaton executable not found!') self.ui.CheckCifLogPlainTextEdit.appendPlainText('You can download Platon at http://www.platonsoft.nl/platon/\n') runner.tick.connect(self.append_to_ciflog_without_newline) diff --git a/finalcif/tools/process.py b/finalcif/tools/process.py index bb011998..d38041e4 100644 --- a/finalcif/tools/process.py +++ b/finalcif/tools/process.py @@ -30,7 +30,7 @@ def __init__(self, parent, output_widget: QPlainTextEdit, log_widget: QPlainText def run_process(self): self._origdir = os.curdir - os.chdir(self.cif_file.parent) + #os.chdir(self.cif_file.parent) self.formula_moiety = '' self.Z = '' self.process = QProcess() @@ -38,12 +38,13 @@ def run_process(self): threading.Thread(target=self._monitor_output_log).start() # self.process.readyReadStandardOutput.connect(self.on_ready_read) self.process.finished.connect(self._onfinished) + self.process.setWorkingDirectory(str(self.cif_file.parent)) self.cif_file.with_suffix('.chk').unlink(missing_ok=True) self.process.start(self.platon_exe, ["-U", str(self.cif_file.name)]) def _onfinished(self): self._on_ready_read() - os.chdir(self._origdir) + #os.chdir(self._origdir) self._parse_chk_file() self.output_widget.setPlainText(self.chk_file_text) self.finished.emit(True) From 28d076412fc39a16b4a9dfde08337e447a7ee42b Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Sun, 10 Sep 2023 07:58:56 +0200 Subject: [PATCH 07/14] Terminate process always --- finalcif/appwindow.py | 2 +- finalcif/tools/process.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/finalcif/appwindow.py b/finalcif/appwindow.py index 2b16e252..e473cfee 100644 --- a/finalcif/appwindow.py +++ b/finalcif/appwindow.py @@ -1014,8 +1014,8 @@ def do_offline_checkcif(self) -> None: self.ui.CheckCifLogPlainTextEdit.setPlainText('\nPlaton executable not found!') self.ui.CheckCifLogPlainTextEdit.appendPlainText('You can download Platon at http://www.platonsoft.nl/platon/\n') runner.tick.connect(self.append_to_ciflog_without_newline) - runner.run_process() runner.finished.connect(lambda: self.ui.CheckcifButton.setEnabled(True)) + runner.run_process() app.processEvents() def append_to_ciflog_without_newline(self, text: str = '') -> None: diff --git a/finalcif/tools/process.py b/finalcif/tools/process.py index d38041e4..19a4ad47 100644 --- a/finalcif/tools/process.py +++ b/finalcif/tools/process.py @@ -67,8 +67,8 @@ def _monitor_output_log(self): def _stop_program(self): self.is_stopped = True - if self.process and self.process.state() == QProcess.Running: - self.process.terminate() + #if self.process and self.process.state() == QProcess.Running: + self.process.terminate() self.finished.emit(True) def _parse_chk_file(self): From c3f01b08627e0da3b086bf3526ae17958549ff77 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Sun, 10 Sep 2023 08:11:09 +0200 Subject: [PATCH 08/14] Also add moiety formula after checkcif --- finalcif/appwindow.py | 7 ++++++- finalcif/tools/process.py | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/finalcif/appwindow.py b/finalcif/appwindow.py index e473cfee..76bf8f32 100644 --- a/finalcif/appwindow.py +++ b/finalcif/appwindow.py @@ -1016,7 +1016,12 @@ def do_offline_checkcif(self) -> None: runner.tick.connect(self.append_to_ciflog_without_newline) runner.finished.connect(lambda: self.ui.CheckcifButton.setEnabled(True)) runner.run_process() - app.processEvents() + runner.formula.connect(self.add_moiety_furmula) + + def add_moiety_furmula(self, formula_moiety): + moiety = self.ui.cif_main_table.getTextFromKey(key='_chemical_formula_moiety', col=Column.CIF) + if formula_moiety and moiety in ['', '?'] and not self.cif.is_multi_cif: + self.ui.cif_main_table.setText(key='_chemical_formula_moiety', txt=formula_moiety, column=Column.EDIT) def append_to_ciflog_without_newline(self, text: str = '') -> None: self.ui.CheckCifLogPlainTextEdit.moveCursor(QtGui.QTextCursor.End) diff --git a/finalcif/tools/process.py b/finalcif/tools/process.py index 19a4ad47..82ab83d7 100644 --- a/finalcif/tools/process.py +++ b/finalcif/tools/process.py @@ -14,6 +14,7 @@ class PlatonRunner(QtCore.QObject): finished = QtCore.pyqtSignal(bool) + formula = QtCore.pyqtSignal(str) tick = QtCore.pyqtSignal(str) def __init__(self, parent, output_widget: QPlainTextEdit, log_widget: QPlainTextEdit, cif_file: Path): @@ -80,6 +81,7 @@ def _parse_chk_file(self): for num, line in enumerate(self.chk_file_text.splitlines(keepends=False)): if line.startswith('# MoietyFormula'): self.formula_moiety = ' '.join(line.split(' ')[2:]) + self.formula.emit(self.formula_moiety) if line.startswith('# Z'): self.Z = line[19:24].strip(' ') From 5479fb75e2646dcf133da505dfe82ff70d4d28c6 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Sun, 10 Sep 2023 08:22:12 +0200 Subject: [PATCH 09/14] Close help on esc --- finalcif/gui/dialogs.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/finalcif/gui/dialogs.py b/finalcif/gui/dialogs.py index 8f323a67..94659ef0 100644 --- a/finalcif/gui/dialogs.py +++ b/finalcif/gui/dialogs.py @@ -102,6 +102,10 @@ def show_keyword_help(parent, helptext: str, title: str = ''): window = QMainWindow(parent=parent) window.setWindowFlags(Qt.Tool) window.setWindowTitle(title) + def close_window(event): + if event.key() == Qt.Key_Escape: + window.close() + window.keyPressEvent = close_window widget = QFrame() layout = QVBoxLayout() button = QPushButton('close') From 323486dfd9da6c91efdaf99b037dbdc784922c90 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Sun, 10 Sep 2023 08:52:19 +0200 Subject: [PATCH 10/14] hide output window --- finalcif/tools/process.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/finalcif/tools/process.py b/finalcif/tools/process.py index 82ab83d7..610a193e 100644 --- a/finalcif/tools/process.py +++ b/finalcif/tools/process.py @@ -39,6 +39,8 @@ def run_process(self): threading.Thread(target=self._monitor_output_log).start() # self.process.readyReadStandardOutput.connect(self.on_ready_read) self.process.finished.connect(self._onfinished) + if sys.platform.startswith('win'): + self.process.setCreateProcessArgumentsModifier(self.hide_window) self.process.setWorkingDirectory(str(self.cif_file.parent)) self.cif_file.with_suffix('.chk').unlink(missing_ok=True) self.process.start(self.platon_exe, ["-U", str(self.cif_file.name)]) @@ -85,6 +87,11 @@ def _parse_chk_file(self): if line.startswith('# Z'): self.Z = line[19:24].strip(' ') + def hide_window(self, args): + # This needs Windows + args[0] = args[0] | 0x08000000 # CREATE_NO_WINDOW flag + return args + @property def platon_exe(self): if sys.platform.startswith('win'): From a335380afef60885b786b5792b92baeff2af4e50 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Sun, 10 Sep 2023 09:24:45 +0200 Subject: [PATCH 11/14] Revert "hide output window" This reverts commit 323486dfd9da6c91efdaf99b037dbdc784922c90. --- finalcif/tools/process.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/finalcif/tools/process.py b/finalcif/tools/process.py index 610a193e..82ab83d7 100644 --- a/finalcif/tools/process.py +++ b/finalcif/tools/process.py @@ -39,8 +39,6 @@ def run_process(self): threading.Thread(target=self._monitor_output_log).start() # self.process.readyReadStandardOutput.connect(self.on_ready_read) self.process.finished.connect(self._onfinished) - if sys.platform.startswith('win'): - self.process.setCreateProcessArgumentsModifier(self.hide_window) self.process.setWorkingDirectory(str(self.cif_file.parent)) self.cif_file.with_suffix('.chk').unlink(missing_ok=True) self.process.start(self.platon_exe, ["-U", str(self.cif_file.name)]) @@ -87,11 +85,6 @@ def _parse_chk_file(self): if line.startswith('# Z'): self.Z = line[19:24].strip(' ') - def hide_window(self, args): - # This needs Windows - args[0] = args[0] | 0x08000000 # CREATE_NO_WINDOW flag - return args - @property def platon_exe(self): if sys.platform.startswith('win'): From 71165eeb2e0a9dacac01ae97b2c55994eb8e7b8b Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Sun, 10 Sep 2023 09:44:44 +0200 Subject: [PATCH 12/14] Update process.py --- finalcif/tools/process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/finalcif/tools/process.py b/finalcif/tools/process.py index 82ab83d7..6098804c 100644 --- a/finalcif/tools/process.py +++ b/finalcif/tools/process.py @@ -53,7 +53,7 @@ def _onfinished(self): def _on_ready_read(self): output = self.process.readAllStandardOutput().data().decode() - self.log_widget.setPlainText(output) + self.log_widget.appendPlainText(output) def _monitor_output_log(self): while not self.is_stopped: From b59c985b0406054af9b016ff71e49fa1ecef2172 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Sun, 10 Sep 2023 10:27:32 +0200 Subject: [PATCH 13/14] remove old platon runner --- finalcif/appwindow.py | 6 +- finalcif/tools/platon.py | 220 +++++++++++++++++++++----------------- finalcif/tools/process.py | 164 ---------------------------- 3 files changed, 125 insertions(+), 265 deletions(-) delete mode 100644 finalcif/tools/process.py diff --git a/finalcif/appwindow.py b/finalcif/appwindow.py index 76bf8f32..143e819d 100644 --- a/finalcif/appwindow.py +++ b/finalcif/appwindow.py @@ -58,8 +58,7 @@ cif_to_header_label, grouper, is_database_number, file_age_in_days, open_file, \ strip_finalcif_of_name from finalcif.tools.options import Options -from finalcif.tools.platon import Platon -from finalcif.tools.process import PlatonRunner +from finalcif.tools.platon import PlatonRunner from finalcif.tools.settings import FinalCifSettings from finalcif.tools.shred import ShredCIF from finalcif.tools.space_groups import SpaceGroups @@ -1012,7 +1011,8 @@ def do_offline_checkcif(self) -> None: cif_file=self.cif.fileobj) if not Path(which(runner.platon_exe)).exists(): self.ui.CheckCifLogPlainTextEdit.setPlainText('\nPlaton executable not found!') - self.ui.CheckCifLogPlainTextEdit.appendPlainText('You can download Platon at http://www.platonsoft.nl/platon/\n') + self.ui.CheckCifLogPlainTextEdit.appendPlainText( + 'You can download Platon at http://www.platonsoft.nl/platon/\n') runner.tick.connect(self.append_to_ciflog_without_newline) runner.finished.connect(lambda: self.ui.CheckcifButton.setEnabled(True)) runner.run_process() diff --git a/finalcif/tools/platon.py b/finalcif/tools/platon.py index 0b5b3e95..6098804c 100644 --- a/finalcif/tools/platon.py +++ b/finalcif/tools/platon.py @@ -1,76 +1,87 @@ -""" -:: Moiety_Formula = C24 H27 Au Cl N P, C H2 Cl2, Cl -""" -# ---------------------------------------------------------------------------- -# "THE BEER-WARE LICENSE" (Revision 42): -# dkratzert@gmx.de> wrote this file. As long as you retain -# this notice you can do whatever you want with this stuff. If we meet some day, -# and you think this stuff is worth it, you can buy me a beer in return. -# Dr. Daniel Kratzert -# ---------------------------------------------------------------------------- - import os import subprocess import sys +import threading +import time from contextlib import suppress from pathlib import Path -from subprocess import TimeoutExpired -from time import sleep -from typing import Union -from PyQt5.QtCore import QThread +from PyQt5 import QtCore +from PyQt5.QtCore import QProcess, QTimer, QTime +from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QLabel, \ + QPlainTextEdit -class Platon(QThread): - def __init__(self, cif: Path, timeout: int = 300, cmdoption='-u'): - """ - Option -u is for checkcif - """ - super().__init__() - self.cmdoption = cmdoption - self.timeout = timeout - self.cif_path = cif - self.chkfile = Path(self.cif_path.with_suffix('.chk')) - self.platon_output = '' +class PlatonRunner(QtCore.QObject): + finished = QtCore.pyqtSignal(bool) + formula = QtCore.pyqtSignal(str) + tick = QtCore.pyqtSignal(str) + + def __init__(self, parent, output_widget: QPlainTextEdit, log_widget: QPlainTextEdit, cif_file: Path): + super().__init__(parent) + self.cif_file = cif_file.resolve().absolute() + self.process = None + self.is_stopped = False + self._origdir = None + self.output_widget = output_widget + self.log_widget = log_widget + self.formula_moiety = '' + self.Z = '' self.chk_file_text = '' + + def run_process(self): + self._origdir = os.curdir + #os.chdir(self.cif_file.parent) self.formula_moiety = '' self.Z = '' + self.process = QProcess() + self.output_widget.clear() + threading.Thread(target=self._monitor_output_log).start() + # self.process.readyReadStandardOutput.connect(self.on_ready_read) + self.process.finished.connect(self._onfinished) + self.process.setWorkingDirectory(str(self.cif_file.parent)) + self.cif_file.with_suffix('.chk').unlink(missing_ok=True) + self.process.start(self.platon_exe, ["-U", str(self.cif_file.name)]) + + def _onfinished(self): + self._on_ready_read() + #os.chdir(self._origdir) + self._parse_chk_file() + self.output_widget.setPlainText(self.chk_file_text) + self.finished.emit(True) self.delete_orphaned_files() - self.plat: Union[subprocess.CompletedProcess, bool] = True - def kill(self): - if sys.platform.startswith('win'): - with suppress(FileNotFoundError): - subprocess.run(["taskkill", "/f", "/im", "platon.exe"], shell=False) - if sys.platform[:5] in ('linux', 'darwi'): - with suppress(FileNotFoundError): - subprocess.run(["killall", "platon"], shell=False) + def _on_ready_read(self): + output = self.process.readAllStandardOutput().data().decode() + self.log_widget.appendPlainText(output) - def delete_orphaned_files(self): - # delete orphaned files: - for ext in ['.ckf', '.fcf', '.def', '.lis', '.sar', '.ckf', - '.sum', '.hkp', '.pjn', '.bin', '.spf']: + def _monitor_output_log(self): + while not self.is_stopped: + self.tick.emit('#') + time.sleep(1) try: - file = self.cif_path.resolve().with_suffix(ext) - if file.stat().st_size < 100: - file.unlink() - if file.suffix in ['.sar', '.spf', '.ckf']: - file.unlink() + log_file = self.cif_file.with_suffix('.chk').read_text('latin1', errors='ignore') + if 'Unresolved or to be Checked Issue' in log_file: + self._stop_program() except FileNotFoundError: - # print('##') - pass + break - def parse_chk_file(self): - """ - """ + def _stop_program(self): + self.is_stopped = True + #if self.process and self.process.state() == QProcess.Running: + self.process.terminate() + self.finished.emit(True) + + def _parse_chk_file(self): try: - self.chk_file_text = self.chkfile.read_text(encoding='ascii', errors='ignore') + self.chk_file_text = self.cif_file.with_suffix('.chk').read_text(encoding='latin1', errors='ignore') except FileNotFoundError as e: print('CHK file not found:', e) self.chk_file_text = '' for num, line in enumerate(self.chk_file_text.splitlines(keepends=False)): if line.startswith('# MoietyFormula'): self.formula_moiety = ' '.join(line.split(' ')[2:]) + self.formula.emit(self.formula_moiety) if line.startswith('# Z'): self.Z = line[19:24].strip(' ') @@ -85,56 +96,69 @@ def platon_exe(self): else: return 'platon' - def run(self): - """ - Runs the platon thread. - """ - curdir = Path(os.curdir).resolve() - with suppress(FileNotFoundError): - self.cif_path.with_suffix('.vrf').unlink() - with suppress(FileNotFoundError): - self.chkfile.unlink() - os.chdir(str(self.cif_path.absolute().parent)) - try: - print('running local platon on', self.cif_path.name) - self.plat = subprocess.run([self.platon_exe, self.cmdoption, self.cif_path.name], - startupinfo=self.hide_status_window(), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - shell=False, - env=os.environ, - timeout=self.timeout) - except TimeoutExpired: - print('PLATON timeout!') - self.platon_output = 'PLATON timeout!' - pass - except Exception as e: - print('Could not run local platon:' + str(e)) - self.platon_output = str(e) - if self.plat and hasattr(self.plat, 'stdout'): - self.platon_output = self.plat.stdout.decode('ascii') - # self.delete_orphaned_files() - os.chdir(curdir) - - @staticmethod - def hide_status_window(): - try: - # This is only available on windows: - si = subprocess.STARTUPINFO() - si.dwFlags = 1 - si.wShowWindow = 0 - except AttributeError: - si = None - return si + def kill(self): + if sys.platform.startswith('win'): + with suppress(FileNotFoundError): + subprocess.run(["taskkill", "/f", "/im", "platon.exe"], shell=False) + if sys.platform[:5] in ('linux', 'darwi'): + with suppress(FileNotFoundError): + subprocess.run(["killall", "platon"], shell=False) + + def delete_orphaned_files(self): + # delete orphaned files: + for ext in ['.ckf', '.fcf', '.def', '.lis', '.sar', '.ckf', + '.sum', '.hkp', '.pjn', '.bin', '.spf']: + try: + file = self.cif_file.resolve().with_suffix(ext) + if file.stat().st_size < 100: + file.unlink(missing_ok=True) + if file.suffix in ['.sar', '.spf', '.ckf']: + file.unlink(missing_ok=True) + except FileNotFoundError: + pass + - def __repr__(self): - return 'Platon:\n{}'.format(self.formula_moiety) +class ProcessWidget(QWidget): + def __init__(self): + super().__init__() + layout = QVBoxLayout() + self.text_widget = QPlainTextEdit() + self.log_widget = QPlainTextEdit() + layout.addWidget(self.log_widget) + layout.addWidget(self.text_widget) + self.button = QPushButton("Run QProcess") + layout.addWidget(self.button) + self.time_label = QLabel() + layout.addWidget(self.time_label) + self.setLayout(layout) + self.runner = PlatonRunner(parent=self, output_widget=self.text_widget, log_widget=self.log_widget, + cif_file=Path("tests/examples/work/cu_BruecknerJK_153F40_0m.cif")) + self.button.clicked.connect(lambda x: self.button.setDisabled(True)) + self.button.clicked.connect(lambda x: self.runner.run_process()) + self.button.clicked.connect(lambda x: self.log_widget.setPlainText('Running Platon')) + self.runner.finished.connect(lambda x: self.button.setEnabled(True)) + + # Only to show that the main thread works continuously: + self.timer = QTimer(self) + self.timer.timeout.connect(self.update_time) + self.timer.start(1000) + + def update_time(self): + current_time = QTime.currentTime() + time_text = current_time.toString("hh:mm:ss") + self.time_label.setText(f"Current Time: {time_text}") if __name__ == '__main__': - fname = Path('test-data/DK_zucker2_0m.cif') - p = Platon(fname) - p.start() - sleep(15) - p.exit() - p.kill() + app = QApplication(sys.argv) + window = QMainWindow() + window.setWindowTitle("QProcess Example") + window.setMinimumWidth(800) + window.setMinimumHeight(600) + + process_widget = ProcessWidget() + window.setCentralWidget(process_widget) + + window.show() + + sys.exit(app.exec_()) diff --git a/finalcif/tools/process.py b/finalcif/tools/process.py deleted file mode 100644 index 6098804c..00000000 --- a/finalcif/tools/process.py +++ /dev/null @@ -1,164 +0,0 @@ -import os -import subprocess -import sys -import threading -import time -from contextlib import suppress -from pathlib import Path - -from PyQt5 import QtCore -from PyQt5.QtCore import QProcess, QTimer, QTime -from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QLabel, \ - QPlainTextEdit - - -class PlatonRunner(QtCore.QObject): - finished = QtCore.pyqtSignal(bool) - formula = QtCore.pyqtSignal(str) - tick = QtCore.pyqtSignal(str) - - def __init__(self, parent, output_widget: QPlainTextEdit, log_widget: QPlainTextEdit, cif_file: Path): - super().__init__(parent) - self.cif_file = cif_file.resolve().absolute() - self.process = None - self.is_stopped = False - self._origdir = None - self.output_widget = output_widget - self.log_widget = log_widget - self.formula_moiety = '' - self.Z = '' - self.chk_file_text = '' - - def run_process(self): - self._origdir = os.curdir - #os.chdir(self.cif_file.parent) - self.formula_moiety = '' - self.Z = '' - self.process = QProcess() - self.output_widget.clear() - threading.Thread(target=self._monitor_output_log).start() - # self.process.readyReadStandardOutput.connect(self.on_ready_read) - self.process.finished.connect(self._onfinished) - self.process.setWorkingDirectory(str(self.cif_file.parent)) - self.cif_file.with_suffix('.chk').unlink(missing_ok=True) - self.process.start(self.platon_exe, ["-U", str(self.cif_file.name)]) - - def _onfinished(self): - self._on_ready_read() - #os.chdir(self._origdir) - self._parse_chk_file() - self.output_widget.setPlainText(self.chk_file_text) - self.finished.emit(True) - self.delete_orphaned_files() - - def _on_ready_read(self): - output = self.process.readAllStandardOutput().data().decode() - self.log_widget.appendPlainText(output) - - def _monitor_output_log(self): - while not self.is_stopped: - self.tick.emit('#') - time.sleep(1) - try: - log_file = self.cif_file.with_suffix('.chk').read_text('latin1', errors='ignore') - if 'Unresolved or to be Checked Issue' in log_file: - self._stop_program() - except FileNotFoundError: - break - - def _stop_program(self): - self.is_stopped = True - #if self.process and self.process.state() == QProcess.Running: - self.process.terminate() - self.finished.emit(True) - - def _parse_chk_file(self): - try: - self.chk_file_text = self.cif_file.with_suffix('.chk').read_text(encoding='latin1', errors='ignore') - except FileNotFoundError as e: - print('CHK file not found:', e) - self.chk_file_text = '' - for num, line in enumerate(self.chk_file_text.splitlines(keepends=False)): - if line.startswith('# MoietyFormula'): - self.formula_moiety = ' '.join(line.split(' ')[2:]) - self.formula.emit(self.formula_moiety) - if line.startswith('# Z'): - self.Z = line[19:24].strip(' ') - - @property - def platon_exe(self): - if sys.platform.startswith('win'): - in_pwt = r'C:\pwt\platon.exe' - else: - in_pwt = 'platon' - if Path(in_pwt).exists(): - return in_pwt - else: - return 'platon' - - def kill(self): - if sys.platform.startswith('win'): - with suppress(FileNotFoundError): - subprocess.run(["taskkill", "/f", "/im", "platon.exe"], shell=False) - if sys.platform[:5] in ('linux', 'darwi'): - with suppress(FileNotFoundError): - subprocess.run(["killall", "platon"], shell=False) - - def delete_orphaned_files(self): - # delete orphaned files: - for ext in ['.ckf', '.fcf', '.def', '.lis', '.sar', '.ckf', - '.sum', '.hkp', '.pjn', '.bin', '.spf']: - try: - file = self.cif_file.resolve().with_suffix(ext) - if file.stat().st_size < 100: - file.unlink(missing_ok=True) - if file.suffix in ['.sar', '.spf', '.ckf']: - file.unlink(missing_ok=True) - except FileNotFoundError: - pass - - -class ProcessWidget(QWidget): - def __init__(self): - super().__init__() - layout = QVBoxLayout() - self.text_widget = QPlainTextEdit() - self.log_widget = QPlainTextEdit() - layout.addWidget(self.log_widget) - layout.addWidget(self.text_widget) - self.button = QPushButton("Run QProcess") - layout.addWidget(self.button) - self.time_label = QLabel() - layout.addWidget(self.time_label) - self.setLayout(layout) - self.runner = PlatonRunner(parent=self, output_widget=self.text_widget, log_widget=self.log_widget, - cif_file=Path("tests/examples/work/cu_BruecknerJK_153F40_0m.cif")) - self.button.clicked.connect(lambda x: self.button.setDisabled(True)) - self.button.clicked.connect(lambda x: self.runner.run_process()) - self.button.clicked.connect(lambda x: self.log_widget.setPlainText('Running Platon')) - self.runner.finished.connect(lambda x: self.button.setEnabled(True)) - - # Only to show that the main thread works continuously: - self.timer = QTimer(self) - self.timer.timeout.connect(self.update_time) - self.timer.start(1000) - - def update_time(self): - current_time = QTime.currentTime() - time_text = current_time.toString("hh:mm:ss") - self.time_label.setText(f"Current Time: {time_text}") - - -if __name__ == '__main__': - app = QApplication(sys.argv) - window = QMainWindow() - window.setWindowTitle("QProcess Example") - window.setMinimumWidth(800) - window.setMinimumHeight(600) - - process_widget = ProcessWidget() - window.setCentralWidget(process_widget) - - window.show() - - sys.exit(app.exec_()) From 822160edeb77ceb8d10b77ea61a82cb6e57266c2 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Sun, 10 Sep 2023 14:53:33 +0200 Subject: [PATCH 14/14] Small fixes --- finalcif/cif/checkcif/checkcif.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/finalcif/cif/checkcif/checkcif.py b/finalcif/cif/checkcif/checkcif.py index f8a9a817..9ecf23f1 100644 --- a/finalcif/cif/checkcif/checkcif.py +++ b/finalcif/cif/checkcif/checkcif.py @@ -16,7 +16,6 @@ import requests from PyQt5.QtCore import QUrl, QThread, pyqtSignal -from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply from requests.exceptions import MissingSchema from finalcif.cif.cif_file_io import CifContainer @@ -76,7 +75,7 @@ def run(self) -> None: req = self._do_the_server_request(headers, temp_cif) if req: self.progress.emit('request finished') - if not req.status_code == 200: + if req.status_code != 200: self.failed.emit('Request failed with code: {}'.format(str(req.status_code))) else: t2 = time.perf_counter() @@ -143,8 +142,8 @@ def fix_iucr_urls(content: str): """ The IuCr checkcif page suddenly contains urls where the protocol is missing. """ - href = re.sub(r'\s+href\s{0,}=\s{0,}"//', ' href="https://', content) - return re.sub(r'\s+src\s{0,}=\s{0,}"//', ' src="https://', href) + href = re.sub(r'\s+href\s*=\s*"//', ' href="https://', content) + return re.sub(r'\s+src\s*=\s*"//', ' src="https://', href) class MyHTMLParser(HTMLParser): @@ -246,8 +245,9 @@ def _parse_checkdef(self, alert: str) -> str: class AlertHelpRemote(): def __init__(self, alert: str): + from PyQt5.QtNetwork import QNetworkAccessManager self.netman = QNetworkAccessManager() - self.helpurl = r'https://journals.iucr.org/services/cif/checking/' + alert + '.html' + self.helpurl = fr'https://journals.iucr.org/services/cif/checking/{alert}.html' print('url:', self.helpurl) self.netman.finished.connect(self._parse_result) @@ -257,7 +257,7 @@ def get_help(self) -> None: print('doing request') self.netman.get(req) - def _parse_result(self, reply: QNetworkReply) -> str: + def _parse_result(self, reply: 'QNetworkReply') -> str: if reply.error(): print(reply.errorString()) print('parsing reply')