diff --git a/README.md b/README.md index dfb481a8bf2d48c4181f3a7b008061353547ea92..e93d880a99d8aa0e9d0323172a236494a900f3c9 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,8 @@ $ pip install git+https://github.com/busimus/cutelog.git ``` ### Requirements -* Python 3.6 (or newer) -* PyQt5 +* Python 3.5 (or newer) +* PyQt5 (preferably 5.6 or newer) ## Usage 1. Start `cutelog` diff --git a/README.rst b/README.rst index 7a7af177c8c973cced7226b8fcc14dc827dbf186..fafc8f0c7a01de9c91c07be90de63e387318c256 100644 --- a/README.rst +++ b/README.rst @@ -38,8 +38,8 @@ Or install the latest development version from the source:: Requirements ------------ -* Python 3.6 (or newer) -* PyQt5 +* Python 3.5 (or newer) +* PyQt5 (preferably 5.6 or newer) Usage ===== diff --git a/cutelog/config.py b/cutelog/config.py index 492a5ef2b8bf3548dcd86871364a4ecb1e71dc88..fa27727c4bb04d88bf4fafa00a7dd404799f4a00 100644 --- a/cutelog/config.py +++ b/cutelog/config.py @@ -2,10 +2,11 @@ import logging import os import sys from collections import namedtuple +from platform import python_version from pkg_resources import get_distribution, resource_filename -from PyQt5.QtCore import (QCoreApplication, QFile, QObject, QSettings, Qt, - pyqtSignal) +from PyQt5.QtCore import (QT_VERSION_STR, QCoreApplication, QFile, QObject, + QSettings, Qt, pyqtSignal) # from PyQt5.QtGui import QFont @@ -18,6 +19,17 @@ else: DEFAULT_FONT = 'Sans' +# @Future: when the time is right, remove everything related to this mess: +PY35_COMPAT = python_version().startswith('3.5') + +# @Future: when Qt 5.6 becomes standard, remove this: +QT_VER = QT_VERSION_STR.split('.') +if QT_VER[0] == '5' and int(QT_VER[1]) < 6: + QT55_COMPAT = True +else: + QT55_COMPAT = False + + # There must be a better way to do this, right? Option = namedtuple('Option', ['name', 'type', 'default']) OPTION_SPEC = ( @@ -69,8 +81,8 @@ class Config(QObject): self.options = None self.option_spec = self.load_option_spec() self.options = self.load_options() - self.full_name = f"{QCoreApplication.applicationName()} "\ - f"{QCoreApplication.applicationVersion()}" + self.full_name = "{} {}".format(QCoreApplication.applicationName(), + QCoreApplication.applicationVersion()) # options that need fast access are also definded as attributes, which # are updated by calling update_attributes() @@ -84,11 +96,11 @@ class Config(QObject): # self.save_options() def __getitem__(self, name): - # self.log.debug(f'Getting "{name}"') + # self.log.debug('Getting "{}"'.format(name)) value = self.options.get(name, None) if value is None: - raise Exception(f'No option with name "{name}"') - # self.log.debug(f'Returning "{value}"') + raise Exception('No option with name "{}"'.format(name)) + # self.log.debug('Returning "{}"'.format(value)) return value @staticmethod @@ -96,13 +108,13 @@ class Config(QObject): data_dir = resource_filename('cutelog', directory) path = os.path.join(data_dir, name) if not os.path.exists(path): - raise FileNotFoundError(f'Resource file not found in this path: "{path}"') + raise FileNotFoundError('Resource file not found in this path: "{}"'.format(path)) return path def get_ui_qfile(self, name): - file = QFile(f':/ui/{name}') + file = QFile(':/ui/{}'.format(name)) if not file.exists(): - raise FileNotFoundError(f'ui file not found: ":/ui/{name}"') + raise FileNotFoundError('ui file not found: ":/ui/{}"'.format(name)) file.open(QFile.ReadOnly) return file @@ -111,7 +123,7 @@ class Config(QObject): host = self.options.get('listen_host', None) port = self.options.get('listen_port', None) if host is None or port is None: - raise Exception('Listen host or port not in options: "{host}:{port}"') + raise Exception('Listen host or port not in options: "{}:{}"'.format(host, port)) return (host, port) def load_option_spec(self): @@ -190,7 +202,7 @@ class Config(QObject): return result def save_header_preset(self, name, columns): - self.log.debug(f'Saving header preset "{name}"') + self.log.debug('Saving header preset "{}"'.format(name)) s = self.qsettings s.beginGroup('Header_Presets') s.beginWriteArray(name, len(columns)) @@ -202,7 +214,7 @@ class Config(QObject): def load_header_preset(self, name): from .logger_table_header import Column - self.log.debug(f'Loading header preset "{name}"') + self.log.debug('Loading header preset "{}"'.format(name)) s = self.qsettings result = [] if name not in self.get_header_presets(): @@ -238,18 +250,24 @@ def init_qt_info(): QCoreApplication.setApplicationName('cutelog') version = get_distribution(QCoreApplication.applicationName()).version QCoreApplication.setApplicationVersion(version) - QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) + if not QT55_COMPAT: # this attribute was introduced in Qt 5.6 + QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) def init_logging(): log = logging.getLogger('CL') term_handler = logging.StreamHandler() + if PY35_COMPAT: + exc = ImportError + else: + exc = ModuleNotFoundError + try: import colorlog fmt = colorlog.ColoredFormatter('%(asctime)s %(log_color)s[%(name)12s:%(lineno)3s' ' %(funcName)18s ]\t%(levelname)-.6s %(message)s') - except ModuleNotFoundError: + except exc: fmt = logging.Formatter('%(asctime)s [%(name)12s:%(lineno)3s ' '%(funcName)18s ]\t%(levelname)-.6s %(message)s') diff --git a/cutelog/level_edit_dialog.py b/cutelog/level_edit_dialog.py index cb00234ba2425a5160d92a0461747d94062cd711..4620bea7e5254d76875f93fe232c16a3f979cf1e 100644 --- a/cutelog/level_edit_dialog.py +++ b/cutelog/level_edit_dialog.py @@ -229,21 +229,21 @@ class LevelEditDialog(QDialog): # Setting the pallette doesn't override the global stylesheet, # which is why I can't just set pallete with needed colors here. - self.previewLine.setStyleSheet(f"""QLineEdit {{ - color: {self.fg.name()}; - background: {self.bg.name()} - }}""") + self.previewLine.setStyleSheet("""QLineEdit {{ + color: {}; + background: {} + }}""".format(self.fg.name(), self.bg.name())) - self.previewLineDark.setStyleSheet(f"""QLineEdit {{ - color: {self.fgDark.name()}; - background: {self.bgDark.name()} - }}""") + self.previewLineDark.setStyleSheet("""QLineEdit {{ + color: {}; + background: {} + }}""".format(self.fgDark.name(), self.bgDark.name())) - self.bgColorPreview.setStyleSheet(f'QLineEdit {{background: {self.bg.name()} }}') - self.fgColorPreview.setStyleSheet(f'QLineEdit {{background: {self.fg.name()} }}') + self.bgColorPreview.setStyleSheet('QLineEdit {{background: {} }}'.format(self.bg.name())) + self.fgColorPreview.setStyleSheet('QLineEdit {{background: {} }}'.format(self.fg.name())) - self.bgColorPreviewDark.setStyleSheet(f'QLineEdit {{ background: {self.bgDark.name()} }}') - self.fgColorPreviewDark.setStyleSheet(f'QLineEdit {{ background: {self.fgDark.name()} }}') + self.bgColorPreviewDark.setStyleSheet('QLineEdit {{ background: {} }}'.format(self.bgDark.name())) + self.fgColorPreviewDark.setStyleSheet('QLineEdit {{ background: {} }}'.format(self.fgDark.name())) font = self.previewLine.font() font.setBold(self.bold) diff --git a/cutelog/listener.py b/cutelog/listener.py index cf238d79ad485fd5d48052295640f9e1a23a5594..a032b36eb0da500edb4f5d7f441abbb752986779 100644 --- a/cutelog/listener.py +++ b/cutelog/listener.py @@ -45,12 +45,13 @@ class LogServer(QTcpServer): err_string = self.errorString() show_critical_dialog(self.main_window, 'Error while starting the server', err_string) else: - self.main_window.set_status(f'Server is listening on {self.host.toString()}:{self.port}...') + address = "{}:{}".format(self.host.toString(), self.port) + self.main_window.set_status('Server is listening on {}...'.format(address)) def incomingConnection(self, socketDescriptor): self.connections += 1 - name = f'Logger {self.connections}' - self.log.info(f'New connection: "{name}"') + name = 'Logger {}'.format(self.connections) + self.log.info('New connection: "{}"'.format(name)) tab_closed = asyncio.Event() new_conn = LogConnection(self, socketDescriptor, name, self.stop_signal, tab_closed, self.log) @@ -71,16 +72,16 @@ class LogServer(QTcpServer): thread.exit() for _, thread in self.threads.items(): if not thread.wait(1000): - self.log.error(f'Thread "{thread}" didn\'t stop in time, attempring termination') + self.log.error('Thread "{}" didn\'t stop in time, terminating...'.format(thread)) thread.terminate() - self.log.error(f'Thread "{thread}" terminated') + self.log.error('Thread "{}" terminated'.format(thread)) self.log.debug('All connections stopped') def cleanup_connection(self, socketDescriptor): try: del self.threads[socketDescriptor] except Exception as e: - self.log.error(f'Bad socketDescriptor: {socketDescriptor}', exc_info=True) + self.log.error('Bad socketDescriptor: {}'.format(socketDescriptor), exc_info=True) # import pdb; pdb.set_trace() @@ -99,7 +100,7 @@ class LogConnection(QThread): self.tab_closed = tab_closed def run(self): - self.log.debug(f'Connection "{self.name}" is starting') + self.log.debug('Connection "{}" is starting'.format(self.name)) def wait_and_read(n_bytes, wait_ms): "Convinience function that simplifies checking for stop events, etc." @@ -116,7 +117,7 @@ class LogConnection(QThread): while True: if sock.state() != sock.ConnectedState or self.need_to_stop(): - self.log.debug(f'Connection "{self.name}" is stopping') + self.log.debug('Connection "{}" is stopping'.format(self.name)) break read_len = wait_and_read(4, 100) if not read_len: @@ -135,7 +136,7 @@ class LogConnection(QThread): sock.disconnectFromHost() sock.close() self.connection_finished.emit(int(self.socketDescriptor)) - self.log.debug(f'Connection "{self.name}" has stopped') + self.log.debug('Connection "{}" has stopped'.format(self.name)) def need_to_stop(self): return any([self.stop_signal.is_set(), self.tab_closed.is_set()]) @@ -170,7 +171,7 @@ class BenchmarkConnection(LogConnection): while True: if self.need_to_stop(): break - d['msg'] = f"hey {c}" + d['msg'] = "hey {}".format(c) t = time.time() d['created'] = t d['msecs'] = t % 1 * 1000 diff --git a/cutelog/log_levels.py b/cutelog/log_levels.py index d4545d7817631697a0d73862ea13f676a1f395b5..56a837465466a1ed5ed148769c1efdf3839be093 100644 --- a/cutelog/log_levels.py +++ b/cutelog/log_levels.py @@ -49,8 +49,9 @@ class LogLevel: self.__dict__[attr] = deepcopy(other_level.__dict__[attr]) def __repr__(self): - return f"{self.__class__.__name__}(levelname={self.levelname}, "\ - "levelno={self.levelno}, enabled={self.enabled})" + return "{}(levelname={}, "\ + "levelno={}, enabled={})".format(self.__class.__name__, self.levelname, + self.levelno, self.enabled) DEFAULT_LEVELS = \ diff --git a/cutelog/logger_tab.py b/cutelog/logger_tab.py index a6cf0c6d3518f205f40e01ba3f8c23173afc877b..178416bab397f1cac1a03b460fce306a19eec6ac 100644 --- a/cutelog/logger_tab.py +++ b/cutelog/logger_tab.py @@ -48,7 +48,7 @@ class TreeNode: return 0 def __repr__(self): - return f"{self.__class__.__name__}(name={self.name}, path={self.path})" + return "{}(name={}, path={})".format(self.__class__.__name__, self.name, self.path) class LogNamespaceTreeModel(QAbstractItemModel): @@ -272,7 +272,7 @@ class RecordFilter(QSortFilterProxyModel): result = True elif not self.selection_includes_children and name == path: result = True - elif self.selection_includes_children and name.startswith(f'{path}.'): + elif self.selection_includes_children and name.startswith('{}.'.format(path)): result = True else: result = False @@ -355,7 +355,7 @@ class LoggerTab(*LoggerTabBase): def __init__(self, name, tab_closed_signal, log, loop, main_window, parent_widget): super().__init__() self.log = log.getChild(name) - self.log.debug(f'Starting a logger named {name}') + self.log.debug('Starting a logger named {}'.format(name)) self.name = name self.main_window = main_window self.parent_widget = parent_widget @@ -500,11 +500,6 @@ class LoggerTab(*LoggerTabBase): self.record_model.max_capacity = max_capacity self.record_model.trim_if_needed() - def header_section_resized(self, index, oldw, neww): - if index == 4: - return - self.log.warn(f"index = {index}, {oldw} -> {neww}") - def eventFilter(self, object, event): if event.type() == QEvent.KeyPress: if event.key() == Qt.Key_Space or event.key() == Qt.Key_Return: @@ -535,7 +530,7 @@ class LoggerTab(*LoggerTabBase): hits = self.filter_model.match(start, SearchRole, s, 1, Qt.MatchWrap | search_flags) if not hits: - self.log.warn(f'No matches for {s}') + self.log.warn('No matches for {}'.format(s)) self.search_start = 0 else: result = hits[0] @@ -644,7 +639,7 @@ class LoggerTab(*LoggerTabBase): disable_all_action = menu.addAction("Disable all") disable_all_action.triggered.connect(self.disable_all_levels) menu.addSeparator() - edit_action = menu.addAction("Edit selected level") + edit_action = menu.addAction("Edit selected level") edit_action.triggered.connect(self.open_edit_level_dialog) menu.popup(self.levelsTable.viewport().mapToGlobal(position)) @@ -804,7 +799,7 @@ class LoggerTab(*LoggerTabBase): records = [] while True: await asyncio.sleep(0.5) - status = f"{self.monitor_count * 2} rows/s" + status = "{} rows/s".format(self.monitor_count * 2) if self.monitor_count == 0: break records.append(self.monitor_count) diff --git a/cutelog/logger_table_header.py b/cutelog/logger_table_header.py index 3f5de9f47c9f2cc033720f21deff1bb9a286a282..4e9a92644a183251edf5eace9c3f89cc7f8471e3 100644 --- a/cutelog/logger_table_header.py +++ b/cutelog/logger_table_header.py @@ -3,7 +3,7 @@ import json from PyQt5.QtCore import Qt, pyqtSignal, QObject, QEvent from PyQt5.QtWidgets import (QDialog, QDialogButtonBox, QListWidget, - QListWidgetItem, QMenu, QVBoxLayout) + QListWidgetItem, QVBoxLayout) from .config import CONFIG @@ -31,7 +31,7 @@ class Column: self.width = int(d['width']) def __repr__(self): - return f"{self.__class__.__name__}(name={self.name}, title={self.title})" + return "{}(name={}, title={})".format(self.__class__.__name__, self.name, self.title) DEFAULT_COLUMNS = [ @@ -195,9 +195,9 @@ class HeaderEditDialog(QDialog): # # def save_preset(self, action): # result = [] - # for i in range(self.columnList.count()): # column list has to be generated here because if - # item = self.columnList.item(i) # you rearrange and save, then what gets saved is - # result.append(item.column) # the un-rearranged list from the table header + # for i in range(self.columnList.count()): # column list has to be generated here because if + # item = self.columnList.item(i) # you rearrange and save, then what gets saved is + # result.append(item.column) # the un-rearranged list from the table header # # name = action.text() # if action.property('new'): diff --git a/cutelog/main_window.py b/cutelog/main_window.py index 2c576bac77db4c8f9b676fda5c540de082931196..071b759d12b44b4b87544bcdd1e23f05360140f8 100644 --- a/cutelog/main_window.py +++ b/cutelog/main_window.py @@ -185,7 +185,7 @@ class MainWindow(*MainWindowBase): await self.stop_signal.wait() self.server.close_server() self.server_running = False - self.log.debug(f'Run got the stop_signal with reason {self.stop_reason}') + self.log.debug('Run got the stop_signal with reason {}'.format(self.stop_reason)) if self.stop_reason == 'restart': # await self.wait_server_closed() continue @@ -199,7 +199,7 @@ class MainWindow(*MainWindowBase): self.finished.set() def on_connection(self, conn, name, tab_closed): - self.log.debug('New connection: "{name}"') + self.log.debug('New connection: "{}"'.format(name)) one_tab_mode = CONFIG['one_tab_mode'] and len(self.loggers_by_name) > 0 if one_tab_mode: @@ -222,7 +222,7 @@ class MainWindow(*MainWindowBase): self.loop.create_task(new_logger.monitor()) def make_logger_name_unique(self, name): - name_f = f"{name} {{}}" + name_f = "{} {{}}".format(name) c = 1 while name in self.loggers_by_name: name = name_f.format(c) @@ -273,21 +273,22 @@ class MainWindow(*MainWindowBase): return d = QInputDialog(self) - d.setLabelText(f'Enter the new name for the "{logger.name}" tab:') - d.setWindowTitle(f'Rename the "{logger.name}" tab') + d.setLabelText('Enter the new name for the "{}" tab:'.format(logger.name)) + d.setWindowTitle('Rename the "{}" tab'.format(logger.name)) d.textValueSelected.connect(self.change_current_tab_name) d.open() def change_current_tab_name(self, new_name): tab, logger, index = self.get_current_tab_logger_index() if new_name in self.loggers_by_name and new_name != logger.name: - show_warning_dialog(self, "Rename error", f'Logger named "{new_name}" already exists.') + show_warning_dialog(self, "Rename error", + 'Logger named "{}" already exists.'.format(new_name)) return - self.log.debug(f'Renaming logger "{logger.name}" to "{new_name}"') + self.log.debug('Renaming logger "{}" to "{}"'.format(logger.name, new_name)) del self.loggers_by_name[logger.name] logger.name = new_name self.loggers_by_name[new_name] = logger - logger.log.name = '.'.join(logger.log.name.split('.')[:-1]) + f'.{new_name}' + logger.log.name = '.'.join(logger.log.name.split('.')[:-1]) + '.{}'.format(new_name) self.connectionTabWidget.setTabText(index, new_name) def trim_records_dialog(self): @@ -298,8 +299,8 @@ class MainWindow(*MainWindowBase): d = QInputDialog(self) d.setInputMode(QInputDialog.IntInput) d.setIntRange(0, 100000000) # because it sets intMaximum to 99 by default. why?? - d.setLabelText(f'Keep this many records out of {logger.record_model.rowCount()}:') - d.setWindowTitle(f'Trim tab records of "{logger.name}" logger') + d.setLabelText('Keep this many records out of {}:'.format(logger.record_model.rowCount())) + d.setWindowTitle('Trim tab records of "{}" logger'.format(logger.name)) d.intValueSelected.connect(self.trim_current_tab_records) d.open() @@ -317,9 +318,9 @@ class MainWindow(*MainWindowBase): d.setIntRange(0, 100000000) # because it sets intMaximum to 99 by default. why?? max_now = logger.record_model.max_capacity max_now = "not set" if max_now is None else max_now - d.setLabelText(f'Set max capacity for "{logger.name}" logger' - f'\nCurrently {max_now}. Set to 0 to disable:') - d.setWindowTitle(f'Set max capacity') + label_str = 'Set max capacity for "{}" logger\nCurrently {}. Set to 0 to disable:' + d.setLabelText(label_str.format(logger.name, max_now)) + d.setWindowTitle('Set max capacity') d.intValueSelected.connect(self.set_max_capacity) d.open() @@ -334,7 +335,7 @@ class MainWindow(*MainWindowBase): d.show() def merge_tabs(self, dst, srcs): - self.log.debug(f'Merging tabs: dst="{dst}", srcs={srcs}') + self.log.debug('Merging tabs: dst="{}", srcs={}'.format(dst, srcs)) dst_logger = self.loggers_by_name[dst] for src_name in srcs: @@ -353,14 +354,14 @@ class MainWindow(*MainWindowBase): self.close_tab(index) def close_tab(self, index): - self.log.debug(f"Tab close requested: {index}") + self.log.debug("Tab close requested: {}".format(index)) tab = self.connectionTabWidget.widget(index) self.connectionTabWidget.removeTab(index) logger = self.destroy_tab(tab) self.destroy_logger(logger) def destroy_tab(self, tab): - "Returns the logger t" + "Returns the logger of the tab" logger = tab.logger tab.setParent(None) del tab @@ -392,12 +393,12 @@ class MainWindow(*MainWindowBase): tab, logger, index = self.get_current_tab_logger_index() if not tab: return - self.log.debug(f"Tab pop out requested: {int(index)}") + self.log.debug("Tab pop out requested: {}".format(int(index))) tab.destroyed.connect(logger.closeEvent) tab.setAttribute(Qt.WA_DeleteOnClose, True) tab.setWindowFlags(Qt.Window) - tab.setWindowTitle(f'cutelog: "{self.connectionTabWidget.tabText(index)}"') + tab.setWindowTitle('cutelog: "{}"'.format(self.connectionTabWidget.tabText(index))) self.connectionTabWidget.removeTab(index) logger.popped_out = True tab.show() @@ -411,7 +412,7 @@ class MainWindow(*MainWindowBase): def pop_in_tabs(self, names): for name in names: - self.log.debug(f'Popping in logger "{name}"') + self.log.debug('Popping in logger "{}"'.format(name)) logger = self.loggers_by_name[name] self.pop_in_tab(logger) diff --git a/cutelog/text_view_dialog.py b/cutelog/text_view_dialog.py index ab0918d2176144143934dfb5e25a07e9c193a5f1..afed2cce60c219456e675ff0ac36e04daac4c6d6 100644 --- a/cutelog/text_view_dialog.py +++ b/cutelog/text_view_dialog.py @@ -1,4 +1,3 @@ -from PyQt5.QtCore import QSize from PyQt5.QtGui import QFont from PyQt5.QtWidgets import (QApplication, QDialog, QHBoxLayout, QLayout, QPlainTextEdit, QPushButton, QSizePolicy, diff --git a/setup.py b/setup.py index fa81a2a654143cf169d94d7e300b6d777b4a0fa0..953500b970584428653c922e827c5c9e101e0986 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from os.path import dirname, join from setuptools import setup -VERSION = '1.0.6' +VERSION = '1.1.0' # def build_qt_resources(): @@ -23,7 +23,7 @@ setup( url="https://github.com/busimus/cutelog/", requires=['PyQt5'], - python_requires=">=3.6", + python_requires=">=3.5", classifiers=[ "Development Status :: 4 - Beta", @@ -36,7 +36,7 @@ setup( "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Operating System :: POSIX :: Linux", "Programming Language :: Python", - "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3 :: Only", "Topic :: Software Development :: Quality Assurance", "Topic :: System :: Logging",