From d2796d27c384d039ac85bc1536252656978ac2fc Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 2 Apr 2025 23:44:58 +0200 Subject: [PATCH 1/3] Move pytest settings to pyproject.toml --- .gitignore | 2 ++ pyproject.toml | 14 ++++++++++---- pytest.ini | 8 -------- 3 files changed, 12 insertions(+), 12 deletions(-) delete mode 100644 pytest.ini diff --git a/.gitignore b/.gitignore index 9a71bb71..81670fef 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,5 @@ ToC.txt # Other /test.py +.config +.local diff --git a/pyproject.toml b/pyproject.toml index 9fefaa8d..d9aa3485 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,10 +61,6 @@ ignore = ["E133", "E221", "E226", "E228", "E241", "W503", "ANN101", "ANN102", "A per-file-ignores = ["tests/*:ANN"] exclude = ["docs/*"] -[tool.autopep8] -max_line_length = 99 -ignore = ["E133", "E221", "E226", "E228", "E241", "W503"] - [tool.pyright] include = ["novelwriter"] exclude = ["**/__pycache__"] @@ -73,6 +69,16 @@ reportIncompatibleMethodOverride = false pythonVersion = "3.10" +[tool.pytest.ini_options] +log_level = "DEBUG" +qt_api = "pyqt6" +markers = [ + "base: Base classes tests", + "core: Core classes tests", + "gui: GUI classes tests", + "serial", +] + [tool.coverage.run] branch = false diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index dbc7a006..00000000 --- a/pytest.ini +++ /dev/null @@ -1,8 +0,0 @@ -[pytest] -log_level = DEBUG -qt_api = pyqt6 -markers = - base: Base classes tests - core: Core classes tests - gui: GUI classes tests - serial From 53a2d0e691255e2355af9138061cee6e24ca10e7 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 3 Apr 2025 01:15:52 +0200 Subject: [PATCH 2/3] Use Ruff for linting and fix a ton of issues --- .github/workflows/syntax.yml | 7 +- docs/source/conf.py | 2 +- novelwriter/__init__.py | 12 +-- novelwriter/common.py | 2 +- novelwriter/config.py | 8 +- novelwriter/constants.py | 2 +- novelwriter/core/buildsettings.py | 4 +- novelwriter/core/coretools.py | 4 +- novelwriter/core/docbuild.py | 4 +- novelwriter/core/itemmodel.py | 3 +- novelwriter/dialogs/projectsettings.py | 2 +- novelwriter/dialogs/quotes.py | 2 +- novelwriter/error.py | 4 +- novelwriter/extensions/eventfilters.py | 2 +- novelwriter/extensions/modified.py | 8 +- novelwriter/formats/todocx.py | 4 +- novelwriter/formats/toodt.py | 6 +- novelwriter/gui/doceditor.py | 12 ++- novelwriter/gui/docviewer.py | 9 +- novelwriter/gui/mainmenu.py | 6 +- novelwriter/gui/projtree.py | 8 +- novelwriter/gui/sidebar.py | 2 +- novelwriter/guimain.py | 4 +- novelwriter/shared.py | 8 +- novelwriter/tools/manuscript.py | 28 ++--- novelwriter/tools/manussettings.py | 8 +- novelwriter/tools/writingstats.py | 4 +- pkgutils.py | 6 +- pyproject.toml | 37 ++++++- requirements-dev.txt | 5 +- tests/conftest.py | 3 +- tests/test_base/test_base_shared.py | 16 +-- tests/test_core/test_core_index.py | 6 +- tests/test_gui/test_gui_doceditor.py | 44 ++++---- tests/test_gui/test_gui_mainmenu.py | 20 ++-- tests/test_text/test_text_counting.py | 32 +++--- tests/test_tools/test_tools_manuscript.py | 4 +- tests/test_tools/test_tools_writingstats.py | 114 ++++++++++---------- tests/tools.py | 14 +-- utils/assets.py | 4 +- utils/build_windows.py | 6 +- utils/common.py | 12 +-- utils/icon_themes.py | 4 +- 43 files changed, 257 insertions(+), 235 deletions(-) diff --git a/.github/workflows/syntax.yml b/.github/workflows/syntax.yml index 5fd90549..a1bf648d 100644 --- a/.github/workflows/syntax.yml +++ b/.github/workflows/syntax.yml @@ -23,11 +23,10 @@ jobs: uses: actions/checkout@v4 - name: Install Dependencies run: pip install -r requirements.txt -r requirements-dev.txt - - name: Run Flake8 + - name: Run Ruff run: | - flake8 --version - flake8 novelwriter --count --show-source --statistics - flake8 tests --count --show-source --statistics --extend-ignore ANN + ruff --version + ruff check - name: Run Pyright run: | pyright --version diff --git a/docs/source/conf.py b/docs/source/conf.py index 9162893d..8b507e58 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -12,7 +12,7 @@ import time # -- Project Information ----------------------------------------------------- project = "novelWriter" -copyright = f"{datetime.date.today().year}" +copyright = f"{datetime.date.today().year}" # noqa: A001 tmp_authors = ["Veronica Berglyd Olsen"] if additional := os.environ.get("SPHINX_I18N_AUTHORS"): diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py index 43b3a4e9..efa6a177 100644 --- a/novelwriter/__init__.py +++ b/novelwriter/__init__.py @@ -159,7 +159,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None: print(helpMsg) sys.exit(0) elif inOpt in ("-v", "--version"): - print("novelWriter Version %s [%s]" % (__version__, __date__)) + print(f"novelWriter Version {__version__} [{__date__}]") sys.exit(0) elif inOpt in ("-i", "--info"): logLevel = logging.INFO @@ -207,17 +207,17 @@ def main(sysArgs: list | None = None) -> GuiMain | None: errorCode = 0 if sys.hexversion < 0x030a00f0: errorData.append( - "At least Python 3.10 is required, found %s" % CONFIG.verPyString + f"At least Python 3.10 is required, found {CONFIG.verPyString}" ) errorCode |= 0x04 if CONFIG.verQtValue < 0x060400: errorData.append( - "At least Qt6 version 6.4 is required, found %s" % CONFIG.verQtString + f"At least Qt6 version 6.4 is required, found {CONFIG.verQtString}" ) errorCode |= 0x08 if CONFIG.verPyQtValue < 0x060400: errorData.append( - "At least PyQt6 version 6.4 is required, found %s" % CONFIG.verPyQtString + f"At least PyQt6 version 6.4 is required, found {CONFIG.verPyQtString}" ) errorCode |= 0x10 @@ -228,9 +228,9 @@ def main(sysArgs: list | None = None) -> GuiMain | None: errDlg.showMessage(( "

A critical error was encountered

" "

novelWriter cannot start due to the following issues:

" - "

 - %s

" + "

 - {0}

" "

Shutting down ...

" - ) % ( + ).format( "
 - ".join(errorData) )) for errLine in errorData: diff --git a/novelwriter/common.py b/novelwriter/common.py index eb793aa9..435efd4c 100644 --- a/novelwriter/common.py +++ b/novelwriter/common.py @@ -490,7 +490,7 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str: """Encode a dictionary, list or tuple as a json object or array, and indent from level n up to a max level nmax if nmax is larger than 0. """ - if not isinstance(data, (dict, list, tuple)): + if not isinstance(data, dict | list | tuple): return "[]" buffer = [] diff --git a/novelwriter/config.py b/novelwriter/config.py index 949f49f3..c5d1f166 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -355,7 +355,7 @@ class Config: """Set the last used path. Only the folder is saved, so if the path is not a folder, the parent of the path is used instead. """ - if isinstance(path, (str, Path)): + if isinstance(path, str | Path): path = checkPath(path, self._homePath) if not path.is_dir(): path = path.parent @@ -504,10 +504,10 @@ class Config: and dataPath is mainly intended for the test suite. """ logger.debug("Initialising Config ...") - if isinstance(confPath, (str, Path)): + if isinstance(confPath, str | Path): logger.info("Setting alternative config path: %s", confPath) self._confPath = Path(confPath) - if isinstance(dataPath, (str, Path)): + if isinstance(dataPath, str | Path): logger.info("Setting alternative data path: %s", dataPath) self._dataPath = Path(dataPath) @@ -555,7 +555,7 @@ class Config: for lngPath, lngBase in langList: for lngCode in self._qLocale.uiLanguages(): qTrans = QTranslator() - lngFile = "%s_%s" % (lngBase, lngCode.replace("-", "_")) + lngFile = "{0}_{1}".format(lngBase, lngCode.replace("-", "_")) if lngFile not in self._qtTrans: if qTrans.load(lngFile, lngPath): logger.debug("Loaded: %s.qm", lngFile) diff --git a/novelwriter/constants.py b/novelwriter/constants.py index 982e574d..9f4c0e1e 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -643,7 +643,7 @@ class nwUnicode: H_LTRIS = "◂" -class nwHtmlUnicode(): +class nwHtmlUnicode: U_TO_H = { # Quotes diff --git a/novelwriter/core/buildsettings.py b/novelwriter/core/buildsettings.py index bad604ff..6c6420b6 100644 --- a/novelwriter/core/buildsettings.py +++ b/novelwriter/core/buildsettings.py @@ -294,12 +294,12 @@ class BuildSettings: def getInt(self, key: str) -> int: """Type safe value access for integers.""" value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None))[1]) - return int(value) if isinstance(value, (int, float)) else 0 + return int(value) if isinstance(value, int | float) else 0 def getFloat(self, key: str) -> float: """Type safe value access for floats.""" value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None))[1]) - return float(value) if isinstance(value, (int, float)) else 0.0 + return float(value) if isinstance(value, int | float) else 0.0 ## # Setters diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py index f118ac7a..d691835e 100644 --- a/novelwriter/core/coretools.py +++ b/novelwriter/core/coretools.py @@ -379,7 +379,7 @@ class ProjectBuilder: """Build or copy a project from a data dictionary.""" if isinstance(data, dict): path = data.get("path", None) or None - if isinstance(path, (str, Path)): + if isinstance(path, str | Path): self._path = Path(path).resolve() if data.get("sample", False): return self._extractSampleProject(self._path) @@ -509,7 +509,7 @@ class ProjectBuilder: # Also add the archive and trash folders project.newRoot(nwItemClass.ARCHIVE) - project.tree.trash # Triggers the creation of Trash + _ = project.tree.trash # Triggers the creation of Trash project.saveProject() project.closeProject() diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py index 2f775fa5..0b10bfe4 100644 --- a/novelwriter/core/docbuild.py +++ b/novelwriter/core/docbuild.py @@ -316,13 +316,13 @@ class NWBuildDocument: bldObj.setStyles(self._build.getBool("html.addStyles")) bldObj.setReplaceUnicode(self._build.getBool("format.stripUnicode")) - if isinstance(bldObj, (ToOdt, ToDocX)): + if isinstance(bldObj, ToOdt | ToDocX): bldObj.setHeaderFormat( self._build.getStr("doc.pageHeader"), self._build.getInt("doc.pageCountOffset"), ) - if isinstance(bldObj, (ToOdt, ToDocX, ToQTextDocument)): + if isinstance(bldObj, ToOdt | ToDocX | ToQTextDocument): scale = nwLabels.UNIT_SCALE.get(self._build.getStr("format.pageUnit"), 1.0) pW, pH = nwLabels.PAPER_SIZE.get(self._build.getStr("format.pageSize"), (-1.0, -1.0)) bldObj.setPageLayout( diff --git a/novelwriter/core/itemmodel.py b/novelwriter/core/itemmodel.py index 4d52aabd..505ee542 100644 --- a/novelwriter/core/itemmodel.py +++ b/novelwriter/core/itemmodel.py @@ -333,8 +333,9 @@ class ProjectModel(QAbstractItemModel): return self.createIndex(parent.row(), 0, parent) return QModelIndex() - def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> QModelIndex: + def index(self, row: int, column: int, parent: QModelIndex | None = None) -> QModelIndex: """Get the index of a child item of a parent.""" + parent = parent or QModelIndex() if self.hasIndex(row, column, parent): node: ProjectNode = parent.internalPointer() if parent.isValid() else self._root if child := node.child(row): diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index beaaf4ba..9c8190be 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -691,7 +691,7 @@ class _ReplacePage(NFixedPage): self.listBox.itemSelectionChanged.connect(self._onSelectionChanged) for aKey, aVal in SHARED.project.data.autoReplace.items(): - newItem = QTreeWidgetItem(["<%s>" % aKey, aVal]) + newItem = QTreeWidgetItem([f"<{aKey}>", aVal]) self.listBox.addTopLevelItem(newItem) self.listBox.sortByColumn(self.C_KEY, Qt.SortOrder.AscendingOrder) diff --git a/novelwriter/dialogs/quotes.py b/novelwriter/dialogs/quotes.py index 20c57801..2f7501f6 100644 --- a/novelwriter/dialogs/quotes.py +++ b/novelwriter/dialogs/quotes.py @@ -78,7 +78,7 @@ class GuiQuoteSelect(NDialog): minSize = 100 for sKey, sLabel in nwQuotes.SYMBOLS.items(): - text = "[ %s ] %s" % (sKey, trConst(sLabel)) + text = f"[ {sKey} ] {trConst(sLabel)}" minSize = max(minSize, metrics.boundingRect(text).width()) qtItem = QListWidgetItem(text) qtItem.setData(self.D_KEY, sKey) diff --git a/novelwriter/error.py b/novelwriter/error.py index 7410cc95..53ac8846 100644 --- a/novelwriter/error.py +++ b/novelwriter/error.py @@ -144,7 +144,7 @@ class NWErrorMessage(QDialog): try: txtTrace = "\n".join(format_tb(exTrace)) - self.msgBody.setPlainText(( + self.msgBody.setPlainText( "Environment:\n" f"novelWriter Version: {__version__}\n" f"Host OS: {sys.platform} ({kernelVersion})\n" @@ -153,7 +153,7 @@ class NWErrorMessage(QDialog): f"enchant: {enchantVersion}\n\n" f"{exType.__name__}:\n{str(exValue)}\n\n" f"Traceback:\n{txtTrace}\n" - )) + ) except Exception: self.msgBody.setPlainText("Failed to generate error report ...") diff --git a/novelwriter/extensions/eventfilters.py b/novelwriter/extensions/eventfilters.py index f305db6c..7e0939b6 100644 --- a/novelwriter/extensions/eventfilters.py +++ b/novelwriter/extensions/eventfilters.py @@ -48,7 +48,7 @@ class WheelEventFilter(QObject): self._locked = False return - def eventFilter(self, object: QObject, event: QEvent) -> bool: + def eventFilter(self, obj: QObject, event: QEvent) -> bool: """Filter events of type QWheelEvent and forward them to the parent widget's wheelEvent handler. """ diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py index 8d20912a..6a095d8e 100644 --- a/novelwriter/extensions/modified.py +++ b/novelwriter/extensions/modified.py @@ -155,15 +155,15 @@ class NDoubleSpinBox(QDoubleSpinBox): self, parent: QWidget | None = None, *, - min: float = 0.0, - max: float = 15.0, + minVal: float = 0.0, + maxVal: float = 15.0, step: float = 0.1, prec: int = 2, ) -> None: super().__init__(parent=parent) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) - self.setMinimum(min) - self.setMaximum(max) + self.setMinimum(minVal) + self.setMaximum(maxVal) self.setSingleStep(step) self.setDecimals(prec) return diff --git a/novelwriter/formats/todocx.py b/novelwriter/formats/todocx.py index ece62a23..76577826 100644 --- a/novelwriter/formats/todocx.py +++ b/novelwriter/formats/todocx.py @@ -212,9 +212,9 @@ class ToDocX(Tokenizer): self._pageMargins = QMargins(_mmToSz(left), _mmToSz(top), _mmToSz(right), _mmToSz(bottom)) return - def setHeaderFormat(self, format: str, offset: int) -> None: + def setHeaderFormat(self, value: str, offset: int) -> None: """Set the document header format.""" - self._headerFormat = format.strip() + self._headerFormat = value.strip() self._pageOffset = offset return diff --git a/novelwriter/formats/toodt.py b/novelwriter/formats/toodt.py index 57599f3b..fce5eec1 100644 --- a/novelwriter/formats/toodt.py +++ b/novelwriter/formats/toodt.py @@ -203,9 +203,9 @@ class ToOdt(Tokenizer): self._mDocRight = f"{right/10.0:.3f}cm" return - def setHeaderFormat(self, format: str, offset: int) -> None: + def setHeaderFormat(self, value: str, offset: int) -> None: """Set the document header format.""" - self._headerFormat = format.strip() + self._headerFormat = value.strip() self._pageOffset = offset return @@ -1509,7 +1509,7 @@ class XMLParagraph: errMsg = "" nMissed = len(self._rawTxt) - self._chrPos if nMissed != 0: - errMsg = "%d char(s) were not written: '%s'" % (nMissed, self._rawTxt) + errMsg = f"{nMissed} char(s) were not written: '{self._rawTxt}'" return nMissed, errMsg ## diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 8638c192..f4d1bff5 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -978,7 +978,7 @@ class GuiDocEditor(QPlainTextEdit): super().dropEvent(event) return - def focusNextPrevChild(self, next: bool) -> bool: + def focusNextPrevChild(self, _next: bool) -> bool: """Capture the focus request from the tab key on the text editor. If the editor has focus, we do not change focus and allow the editor to insert a tab. If the search bar has focus, @@ -1037,7 +1037,7 @@ class GuiDocEditor(QPlainTextEdit): logger.error("Invalid keyword '%s'", keyword) return False logger.debug("Inserting keyword '%s'", keyword) - state = self.insertNewBlock("%s: " % keyword) + state = self.insertNewBlock(f"{keyword}: ") return state @pyqtSlot() @@ -1528,10 +1528,10 @@ class GuiDocEditor(QPlainTextEdit): if fLen == min(numA, numB): cursor.beginEditBlock() cursor.setPosition(posS) - for i in range(fLen): + for _ in range(fLen): cursor.deletePreviousChar() cursor.setPosition(posE) - for i in range(fLen): + for _ in range(fLen): cursor.deletePreviousChar() cursor.endEditBlock() @@ -1943,7 +1943,9 @@ class GuiDocEditor(QPlainTextEdit): exist = False cPos = cursor.selectionStart() - block.position() tExist = SHARED.project.index.checkThese(tBits, self._docHandle) - for sTag, sPos, sExist in zip(reversed(tBits), reversed(tPos), reversed(tExist)): + for sTag, sPos, sExist in zip( + reversed(tBits), reversed(tPos), reversed(tExist), strict=False + ): if cPos >= sPos: # The cursor is between the start of two tags if cPos <= sPos + len(sTag): diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index b94043d1..7805f229 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -610,12 +610,9 @@ class GuiDocViewHistory: for loop, it is skipped entirely if log level isn't DEBUG. """ if CONFIG.isDebug: # pragma: no cover - for i, (h, p) in enumerate(zip(self._navHistory, self._posHistory)): - logger.debug( - "History %02d: %s %13s [x:%d]" % ( - i + 1, ">" if i == self._currPos else " ", h, p - ) - ) + for i, (h, p) in enumerate(zip(self._navHistory, self._posHistory, strict=False)): + a = ">" if i == self._currPos else " " + logger.debug(f"History {i + 1:02d}: {a} {h:13s} [x:{p}]") return diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index 710f67cf..7bc2d196 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -936,7 +936,7 @@ class GuiMainMenu(QMenuBar): # Search > Find in Project self.aFindProj = qtAddAction(self.srcMenu, self.tr("Find in Project")) self.aFindProj.setShortcut("Ctrl+Shift+F") - self.aFindProj.triggered.connect(lambda: self.requestViewChange.emit(nwView.SEARCH)) + self.aFindProj.triggered.connect(qtLambda(self.requestViewChange.emit, nwView.SEARCH)) return @@ -956,10 +956,10 @@ class GuiMainMenu(QMenuBar): self.mSelectLanguage = qtAddMenu(self.toolsMenu, self.tr("Spell Check Language")) languages = SHARED.spelling.listDictionaries() languages.insert(0, ("None", self.tr("Default"))) - for n, (tag, language) in enumerate(languages): + for tag, language in languages: aSpell = QAction(self.mSelectLanguage) aSpell.setText(language) - aSpell.triggered.connect(lambda n, tag=tag: self._changeSpelling(tag)) + aSpell.triggered.connect(qtLambda(self._changeSpelling, tag)) self.mSelectLanguage.addAction(aSpell) # Tools > Re-Run Spell Check diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 7e15c2fa..721324f7 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -259,17 +259,17 @@ class GuiProjectToolBar(QWidget): self.mQuick = QMenu(self) self.tbQuick = NIconToolButton(self, iSz) - self.tbQuick.setToolTip("%s [Ctrl+L]" % self.tr("Quick Links")) + self.tbQuick.setToolTip("{0} [Ctrl+L]".format(self.tr("Quick Links"))) self.tbQuick.setShortcut("Ctrl+L") self.tbQuick.setMenu(self.mQuick) # Move Buttons self.tbMoveU = NIconToolButton(self, iSz) - self.tbMoveU.setToolTip("%s [Ctrl+Up]" % self.tr("Move Up")) + self.tbMoveU.setToolTip("{0} [Ctrl+Up]".format(self.tr("Move Up"))) self.tbMoveU.clicked.connect(self.projTree.moveItemUp) self.tbMoveD = NIconToolButton(self, iSz) - self.tbMoveD.setToolTip("%s [Ctrl+Down]" % self.tr("Move Down")) + self.tbMoveD.setToolTip("{0} [Ctrl+Down]".format(self.tr("Move Down"))) self.tbMoveD.clicked.connect(self.projTree.moveItemDown) # Add Item Menu @@ -309,7 +309,7 @@ class GuiProjectToolBar(QWidget): self._buildRootMenu() self.tbAdd = NIconToolButton(self, iSz) - self.tbAdd.setToolTip("%s [Ctrl+N]" % self.tr("Add Item")) + self.tbAdd.setToolTip("{0} [Ctrl+N]".format(self.tr("Add Item"))) self.tbAdd.setShortcut("Ctrl+N") self.tbAdd.setMenu(self.mAdd) diff --git a/novelwriter/gui/sidebar.py b/novelwriter/gui/sidebar.py index 8e6d16f6..e69b1dad 100644 --- a/novelwriter/gui/sidebar.py +++ b/novelwriter/gui/sidebar.py @@ -155,4 +155,4 @@ class _PopRightMenu(QMenu): if isinstance(parent := self.parent(), QWidget): offset = QPoint(parent.width(), parent.height() - self.height()) self.move(parent.mapToGlobal(offset)) - return super(_PopRightMenu, self).event(event) + return super().event(event) diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 3f5ca87d..ffff4560 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -370,7 +370,7 @@ class GuiMain(QMainWindow): return True if not isYes: - msgYes = SHARED.question("%s
%s" % ( + msgYes = SHARED.question("{0}
{1}".format( self.tr("Close the current project?"), self.tr("Changes are saved automatically.") )) @@ -844,7 +844,7 @@ class GuiMain(QMainWindow): def closeMain(self) -> bool: """Save everything, and close novelWriter.""" - if SHARED.hasProject and CONFIG.askBeforeExit and not SHARED.question("%s
%s" % ( + if SHARED.hasProject and CONFIG.askBeforeExit and not SHARED.question("{0}
{1}".format( self.tr("Do you want to exit novelWriter?"), self.tr("Changes are saved automatically.") )): diff --git a/novelwriter/shared.py b/novelwriter/shared.py index 17b0a58f..9fbd5581 100644 --- a/novelwriter/shared.py +++ b/novelwriter/shared.py @@ -101,28 +101,28 @@ class SharedData(QObject): def mainGui(self) -> GuiMain: """Return the Main GUI instance.""" if self._gui is None: - raise Exception("SharedData class not fully initialised") + raise RuntimeError("SharedData class not fully initialised") return self._gui @property def theme(self) -> GuiTheme: """Return the GUI Theme instance.""" if self._theme is None: - raise Exception("SharedData class not fully initialised") + raise RuntimeError("SharedData class not fully initialised") return self._theme @property def project(self) -> NWProject: """Return the active NWProject instance.""" if self._project is None: - raise Exception("SharedData class not fully initialised") + raise RuntimeError("SharedData class not fully initialised") return self._project @property def spelling(self) -> NWSpellEnchant: """Return the active NWProject instance.""" if self._spelling is None: - raise Exception("SharedData class not fully initialised") + raise RuntimeError("SharedData class not fully initialised") return self._spelling @property diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 46f5727e..f06505d9 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -333,7 +333,7 @@ class GuiManuscript(NToolDialog): def _deleteSelectedBuild(self) -> None: """Delete the currently selected build settings entry.""" if build := self._getSelectedBuild(): - if SHARED.question(self.tr("Delete build '{0}'?".format(build.name))): + if SHARED.question(self.tr("Delete build '{0}'?").format(build.name)): if dialog := self._findSettingsDialog(build.buildID): dialog.close() self._builds.removeBuild(build.buildID) @@ -977,23 +977,23 @@ class _StatsWidget(QWidget): def updateStats(self, data: dict[str, int]) -> None: """Update the stats values from a Tokenizer stats dict.""" # Minimal - self.minWordCount.setText("{0:n}".format(data.get(nwStats.WORDS, 0))) - self.minCharCount.setText("{0:n}".format(data.get(nwStats.CHARS, 0))) + self.minWordCount.setText(f"{data.get(nwStats.WORDS, 0):n}") + self.minCharCount.setText(f"{data.get(nwStats.CHARS, 0):n}") # Maximal - self.maxTotalWords.setText("{0:n}".format(data.get(nwStats.WORDS, 0))) - self.maxHeadWords.setText("{0:n}".format(data.get(nwStats.WORDS_TITLE, 0))) - self.maxTextWords.setText("{0:n}".format(data.get(nwStats.WORDS_TEXT, 0))) - self.maxTitleCount.setText("{0:n}".format(data.get(nwStats.TITLES, 0))) - self.maxParCount.setText("{0:n}".format(data.get(nwStats.PARAGRAPHS, 0))) + self.maxTotalWords.setText(f"{data.get(nwStats.WORDS, 0):n}") + self.maxHeadWords.setText(f"{data.get(nwStats.WORDS_TITLE, 0):n}") + self.maxTextWords.setText(f"{data.get(nwStats.WORDS_TEXT, 0):n}") + self.maxTitleCount.setText(f"{data.get(nwStats.TITLES, 0):n}") + self.maxParCount.setText(f"{data.get(nwStats.PARAGRAPHS, 0):n}") - self.maxTotalChars.setText("{0:n}".format(data.get(nwStats.CHARS, 0))) - self.maxHeaderChars.setText("{0:n}".format(data.get(nwStats.CHARS_TITLE, 0))) - self.maxTextChars.setText("{0:n}".format(data.get(nwStats.CHARS_TEXT, 0))) + self.maxTotalChars.setText(f"{data.get(nwStats.CHARS, 0):n}") + self.maxHeaderChars.setText(f"{data.get(nwStats.CHARS_TITLE, 0):n}") + self.maxTextChars.setText(f"{data.get(nwStats.CHARS_TEXT, 0):n}") - self.maxTotalWordChars.setText("{0:n}".format(data.get(nwStats.WCHARS_ALL, 0))) - self.maxHeadWordChars.setText("{0:n}".format(data.get(nwStats.WCHARS_TITLE, 0))) - self.maxTextWordChars.setText("{0:n}".format(data.get(nwStats.WCHARS_TEXT, 0))) + self.maxTotalWordChars.setText(f"{data.get(nwStats.WCHARS_ALL, 0):n}") + self.maxHeadWordChars.setText(f"{data.get(nwStats.WCHARS_TITLE, 0):n}") + self.maxTextWordChars.setText(f"{data.get(nwStats.WCHARS_TEXT, 0):n}") return diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index 4e182a23..f0f6d1c0 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -228,8 +228,8 @@ class GuiBuildSettings(NToolDialog): """ if self._build.changed: response = SHARED.question(self.tr( - "Do you want to save your changes to '{0}'?".format(self._build.name) - )) + "Do you want to save your changes to '{0}'?" + ).format(self._build.name)) if response: self._emitBuildData() self._build.resetChangedState() @@ -1168,11 +1168,11 @@ class _FormattingTab(NScrollableForm): for key, name in nwLabels.PAPER_NAME.items(): self.pageSize.addItem(trConst(name), key) - self.pageWidth = NDoubleSpinBox(self, max=500.0) + self.pageWidth = NDoubleSpinBox(self, maxVal=500.0) self.pageWidth.setFixedWidth(dbW) self.pageWidth.valueChanged.connect(self._pageSizeValueChanged) - self.pageHeight = NDoubleSpinBox(self, max=500.0) + self.pageHeight = NDoubleSpinBox(self, maxVal=500.0) self.pageHeight.setFixedWidth(dbW) self.pageHeight.valueChanged.connect(self._pageSizeValueChanged) diff --git a/novelwriter/tools/writingstats.py b/novelwriter/tools/writingstats.py index acf8d308..3a8aab06 100644 --- a/novelwriter/tools/writingstats.py +++ b/novelwriter/tools/writingstats.py @@ -447,7 +447,7 @@ class GuiWritingStats(NToolDialog): rType = record.get("type") if rType == "initial": self.wordOffset = checkInt(record.get("offset"), 0) - logger.debug("Initial word count when log was started is %d" % self.wordOffset) + logger.debug("Initial word count when log was started is %d", self.wordOffset) elif rType == "record": try: dStart = datetime.fromisoformat(str(record.get("start"))) @@ -570,7 +570,7 @@ class GuiWritingStats(NToolDialog): idleEntry = formatTime(sIdle) else: sRatio = sIdle/sDiff if sDiff > 0.0 else 0.0 - idleEntry = "%d %%" % round(100.0 * sRatio) + idleEntry = f"{round(100.0 * sRatio)} %" newItem = QTreeWidgetItem() newItem.setText(self.C_TIME, sStart) diff --git a/pkgutils.py b/pkgutils.py index 80281d5c..8062ae2c 100755 --- a/pkgutils.py +++ b/pkgutils.py @@ -102,11 +102,11 @@ def cleanBuildDirs(args: argparse.Namespace) -> None: if folder.is_dir(): try: shutil.rmtree(folder) - print("Deleted: %s" % folder) + print(f"Deleted: {folder}") except OSError: - print("Failed: %s" % folder) + print(f"Failed: {folder}") else: - print("Missing: %s" % folder) + print(f"Missing: {folder}") print("") diff --git a/pyproject.toml b/pyproject.toml index d9aa3485..b8073e0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,17 +55,44 @@ force_grid_wrap = 0 lines_between_types = 1 forced_separate = ["tests.*"] -[tool.flake8] -max-line-length = 99 -ignore = ["E133", "E221", "E226", "E228", "E241", "W503", "ANN101", "ANN102", "ANN401"] -per-file-ignores = ["tests/*:ANN"] -exclude = ["docs/*"] +[tool.ruff] +line-length = 99 + +[tool.ruff.lint] +select = [ + "A", # flake8-builtins (A) + "B", # flake8-bugbear (B) + "E", # pycodestyle (E) + "F", # Pyflakes (F) + "W", # pycodestyle (W) + "UP", +] +ignore = [ + "E221", # multiple-spaces-before-operator + "E226", # missing-whitespace-around-arithmetic-operator + "E228", # missing-whitespace-around-modulo-operator + "E241", # multiple-spaces-after-comma + "ANN401", + "UP015", # redundant-open-modes + "UP030", # format-literals +] + +[tool.ruff.lint.per-file-ignores] +"tests/*" = [] + +[tool.ruff.format] +quote-style = "double" [tool.pyright] include = ["novelwriter"] exclude = ["**/__pycache__"] reportIncompatibleMethodOverride = false +reportGeneralTypeIssues = "information" +reportOptionalMemberAccess = "information" +reportOptionalOperand = "information" +reportOptionalSubscript = "information" +reportUnboundVariable = "error" pythonVersion = "3.10" diff --git a/requirements-dev.txt b/requirements-dev.txt index 919e83d0..f4e05a41 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,3 @@ -flake8 -flake8-annotations -flake8-pep585 -flake8-pyproject +ruff isort pyright diff --git a/tests/conftest.py b/tests/conftest.py index 07689862..3dffc8c6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -195,8 +195,7 @@ def mockRnd(monkeypatch): self.reset() def _rnd(self, n): - for x in range(n): - yield x + yield from range(n) def reset(self): gen = self._rnd(1000) diff --git a/tests/test_base/test_base_shared.py b/tests/test_base/test_base_shared.py index 4f147b2b..8873b5b3 100644 --- a/tests/test_base/test_base_shared.py +++ b/tests/test_base/test_base_shared.py @@ -41,14 +41,14 @@ def testBaseSharedData_Init(): shared = SharedData() # When not initialised, it should raise exceptions - with pytest.raises(Exception): - shared.mainGui - with pytest.raises(Exception): - shared.theme - with pytest.raises(Exception): - shared.project - with pytest.raises(Exception): - shared.spelling + with pytest.raises(RuntimeError): + _ = shared.mainGui + with pytest.raises(RuntimeError): + _ = shared.theme + with pytest.raises(RuntimeError): + _ = shared.project + with pytest.raises(RuntimeError): + _ = shared.spelling # Create some mock objects mockGui = MockGuiMain() diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py index 22d06c11..d58fbf15 100644 --- a/tests/test_core/test_core_index.py +++ b/tests/test_core/test_core_index.py @@ -885,9 +885,9 @@ def testCoreIndex_ExtractData(nwGUI, fncPath, mockRnd): sHandle = project.newFile("Scene One", C.hNovelRoot) tHandle = project.newFile("Scene Two", C.hNovelRoot) - project.tree[hHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore - project.tree[sHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore - project.tree[tHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore + assert project.tree[hHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore + assert project.tree[sHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore + assert project.tree[tHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore assert index.scanText(hHandle, "## Chapter One\n\n") # type: ignore assert index.scanText(sHandle, "### Scene One\n\n") # type: ignore diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index 7d2d4c52..be76f526 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -74,7 +74,7 @@ def testGuiEditor_Init(qtbot, nwGUI, projPath, ipsumText, mockRnd): assert nwGUI.openDocument(C.hSceneDoc) docEditor = nwGUI.docEditor - docEditor.setPlainText("### Lorem Ipsum\n\n%s" % ipsumText[0]) + docEditor.setPlainText(f"### Lorem Ipsum\n\n{ipsumText[0]}") nwGUI.saveDocument() # Check Defaults @@ -147,7 +147,7 @@ def testGuiEditor_LoadText(qtbot, nwGUI, projPath, ipsumText, mockRnd): assert nwGUI.openDocument(C.hSceneDoc) is True docEditor = nwGUI.docEditor - longText = "### Lorem Ipsum\n\n%s" % "\n\n".join(ipsumText*20) + longText = "### Lorem Ipsum\n\n{0}".format("\n\n".join(ipsumText*20)) docEditor.replaceText(longText) nwGUI.saveDocument() nwGUI.closeDocument() @@ -179,7 +179,7 @@ def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, projPath, ipsumTex assert nwGUI.openDocument(C.hSceneDoc) is True docEditor = nwGUI.docEditor - longText = "### Lorem Ipsum\n\n%s" % "\n\n".join(ipsumText) + longText = "### Lorem Ipsum\n\n{0}".format("\n\n".join(ipsumText)) docEditor.replaceText(longText) # Missing item @@ -483,7 +483,7 @@ def testGuiEditor_SpellChecking(qtbot, monkeypatch, nwGUI, projPath, ipsumText, assert nwGUI.openDocument(C.hSceneDoc) is True docEditor = nwGUI.docEditor - text = "### A Scene\n\n%s" % "\n\n".join(ipsumText) + text = "### A Scene\n\n{0}".format("\n\n".join(ipsumText)) docEditor.replaceText(text) # Toggle State @@ -584,7 +584,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd): assert nwGUI.openDocument(C.hSceneDoc) is True docEditor = nwGUI.docEditor - text = "### A Scene\n\n%s" % "\n\n".join(ipsumText) + text = "### A Scene\n\n{0}".format("\n\n".join(ipsumText)) docEditor.replaceText(text) doc = docEditor.document() @@ -650,7 +650,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd): # Emphasis/Undo/Redo # ================== - text = "### A Scene\n\n%s" % ipsumText[0] + text = f"### A Scene\n\n{ipsumText[0]}" docEditor.replaceText(text) # Emphasis @@ -683,7 +683,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd): # Shortcodes # ========== - text = "### A Scene\n\n%s" % ipsumText[0] + text = f"### A Scene\n\n{ipsumText[0]}" docEditor.replaceText(text) # Italic @@ -738,7 +738,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd): # Quotes # ====== - text = "### A Scene\n\n%s" % ipsumText[0] + text = f"### A Scene\n\n{ipsumText[0]}" docEditor.replaceText(text) # Add Single Quotes @@ -772,7 +772,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd): # Remove Line Breaks # ================== - text = "### A Scene\n\n%s" % ipsumText[0] + text = f"### A Scene\n\n{ipsumText[0]}" repText = text[:100] + text[100:].replace(" ", "\n", 3) docEditor.replaceText(repText) assert docEditor.docAction(nwDocAction.RM_BREAKS) is True @@ -974,7 +974,7 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd buildTestProject(nwGUI, projPath) assert nwGUI.openDocument(C.hSceneDoc) is True docEditor = nwGUI.docEditor - text = "### A Scene\n\n%s" % ipsumText[0] + text = f"### A Scene\n\n{ipsumText[0]}" # Insert Text # =========== @@ -1037,7 +1037,7 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd # Insert KeyWords # =============== - text = "### A Scene\n\n\n%s" % ipsumText[0] + text = f"### A Scene\n\n\n{ipsumText[0]}" docEditor.replaceText(text) docEditor.setCursorLine(3) @@ -1076,13 +1076,13 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd): assert nwGUI.openDocument(C.hSceneDoc) is True docEditor = nwGUI.docEditor - text = "### A Scene\n\n%s" % "\n\n".join(ipsumText) + text = "### A Scene\n\n{0}".format("\n\n".join(ipsumText)) docEditor.replaceText(text) # Wrap Selection # ============== - text = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2]) + text = "### A Scene\n\n{0}".format("\n\n".join(ipsumText[0:2])) docEditor.replaceText(text) docEditor.setCursorPosition(45) @@ -1114,7 +1114,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd): # Toggle Format # ============= - text = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2]) + text = "### A Scene\n\n{0}".format("\n\n".join(ipsumText[0:2])) # Block format repetition docEditor.replaceText(text) @@ -1186,7 +1186,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd): # ============== # No Selection - text = "### A Scene\n\n%s" % ipsumText[0].replace("consectetur", "=consectetur=") + text = "### A Scene\n\n{0}".format(ipsumText[0].replace("consectetur", "=consectetur=")) docEditor.replaceText(text) docEditor.setCursorPosition(45) docEditor._replaceQuotes("=", "<", ">") @@ -1194,7 +1194,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd): # First Paragraph Selected # This should not replace anything in second paragraph - text = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2]).replace("ipsum", "=ipsum=") + text = "### A Scene\n\n{0}".format("\n\n".join(ipsumText[0:2]).replace("ipsum", "=ipsum=")) docEditor.replaceText(text) docEditor.setCursorPosition(45) assert docEditor.docAction(nwDocAction.SEL_PARA) @@ -1222,7 +1222,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd): # Check Blocks cursor = docEditor.textCursor() cursor.clearSelection() - text = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo) + text = f"### A Scene\n\n{parOne}\n\n{parTwo}" docEditor.replaceText(text) docEditor.setCursorPosition(45) assert len(docEditor._selectedBlocks(cursor)) == 0 @@ -1231,15 +1231,15 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd): assert len(docEditor._selectedBlocks(cursor)) == 15 # Remove All - text = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo) + text = f"### A Scene\n\n{parOne}\n\n{parTwo}" docEditor.replaceText(text) docEditor.setCursorPosition(45) docEditor._removeInParLineBreaks() - assert docEditor.getText() == "### A Scene\n\n%s\n" % "\n\n".join(ipsumText[0:2]) + assert docEditor.getText() == "### A Scene\n\n{0}\n".format("\n\n".join(ipsumText[0:2])) # Remove in First Paragraph # Second paragraphs should remain unchanged - text = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo) + text = f"### A Scene\n\n{parOne}\n\n{parTwo}" docEditor.replaceText(text) cursor = docEditor.textCursor() cursor.setPosition(16, QtMoveAnchor) @@ -1260,7 +1260,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd): # Key Press Events # ================ - text = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo) + text = f"### A Scene\n\n{parOne}\n\n{parTwo}" docEditor.replaceText(text) assert docEditor.getText() == text @@ -1290,7 +1290,7 @@ def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, projPath, ipsumText # Invalid and Generic # =================== - text = "### A Scene\n\n%s" % ipsumText[0] + text = f"### A Scene\n\n{ipsumText[0]}" docEditor.replaceText(text) # Invalid Block diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py index 8f933760..ec29a233 100644 --- a/tests/test_gui/test_gui_mainmenu.py +++ b/tests/test_gui/test_gui_mainmenu.py @@ -276,11 +276,11 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum): # Other Checks # Replace Quotes - docEditor.setPlainText(( + docEditor.setPlainText( "### New Text\n\n" "Text with 'single' quotes and 'tricky stuff's'.\n\n" "Also text with \"double\" quotes which are \"less tricky\".\n\n" - )) + ) mainMenu.aSelectAll.activate(QAction.ActionEvent.Trigger) mainMenu.aFmtReplSng.activate(QAction.ActionEvent.Trigger) @@ -299,14 +299,14 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum): ) # Remove in-paragraph line breaks - docEditor.setPlainText(( + docEditor.setPlainText( "### New Text\n\n" "@char: Someone\n" "@location: Somewhere\n\n" "% Some comment ...\n\n" "Here is some text\non multiple\nlines.\n\n" "With another paragraph\nhere." - )) + ) mainMenu.aFmtRmBreaks.activate(QAction.ActionEvent.Trigger) assert docEditor.getText() == ( "### New Text\n\n" @@ -317,14 +317,14 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum): "With another paragraph here.\n" ) - docEditor.setPlainText(( + docEditor.setPlainText( "### New Text\n\n" "@char: Someone\n" "@location: Somewhere\n\n" "% Some comment ...\n\n" "Here is some text\non multiple\nlines.\n\n" "With another paragraph\nhere." - )) + ) cursor = docEditor.textCursor() cursor.setPosition(74) cursor.movePosition(QtMoveRight, QtKeepAnchor, 29) @@ -343,12 +343,12 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum): assert not docEditor.docAction(nwDocAction.NO_ACTION) # Test Invalid Formats - docEditor.setPlainText(( + docEditor.setPlainText( "### New Text\n\n" "@tag: Bod\n\n" "Text with 'single' quotes and 'tricky stuff's'.\n\n" "Also text with \"double\" quotes which are \"less tricky\".\n\n" - )) + ) # Cannot Format Tag docEditor.setCursorPosition(17) @@ -489,7 +489,7 @@ def testGuiMainMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd # Insert Keywords # =============== - for action, key in zip(mainMenu.mInsKeywords.actions(), nwKeyWords.ALL_KEYS): + for action, key in zip(mainMenu.mInsKeywords.actions(), nwKeyWords.ALL_KEYS, strict=False): docEditor.setPlainText("Stuff") action.activate(QAction.ActionEvent.Trigger) assert docEditor.getText() == f"Stuff\n{key}: " @@ -503,7 +503,7 @@ def testGuiMainMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd # Insert Fields # ============= - for action, field in zip(mainMenu.mInsField.actions(), nwStats.ALL_FIELDS): + for action, field in zip(mainMenu.mInsField.actions(), nwStats.ALL_FIELDS, strict=False): value = nwShortcode.FIELD_VALUE.format(field) docEditor.setPlainText("Stuff ") docEditor.setCursorPosition(6) diff --git a/tests/test_text/test_text_counting.py b/tests/test_text/test_text_counting.py index 4b360a06..8accab95 100644 --- a/tests/test_text/test_text_counting.py +++ b/tests/test_text/test_text_counting.py @@ -86,7 +86,7 @@ def testTextCounting_standardCounter(): assert standardCounter(" <") == (0, 0, 0) # General Text - cC, wC, pC = standardCounter(( + cC, wC, pC = standardCounter( "#! Title\n\n" "##! Prologue\n\n" "# Heading One\n" @@ -100,47 +100,47 @@ def testTextCounting_standardCounter(): "The second paragraph.\n\n\n" "The third paragraph.\n\n" "Dashes\u2013and even longer\u2014dashes." - )) + ) assert cC == 163 assert wC == 26 assert pC == 4 # Text Alignment - cC, wC, pC = standardCounter(( + cC, wC, pC = standardCounter( "# Title\n\n" "Left aligned<<\n\n" "Left aligned <<\n\n" "Right indent<\n\n" "Right indent <\n\n" - )) + ) assert cC == 53 assert wC == 9 assert pC == 4 - cC, wC, pC = standardCounter(( + cC, wC, pC = standardCounter( "# Title\n\n" ">>Right aligned\n\n" ">> Right aligned\n\n" ">Left indent\n\n" "> Left indent\n\n" - )) + ) assert cC == 53 assert wC == 9 assert pC == 4 - cC, wC, pC = standardCounter(( + cC, wC, pC = standardCounter( "# Title\n\n" ">>Centre aligned<<\n\n" ">> Centre aligned <<\n\n" ">Double indent<\n\n" "> Double indent <\n\n" - )) + ) assert cC == 59 assert wC == 9 assert pC == 4 # Formatting Codes, Upper Case (Old Implementation) - cC, wC, pC = standardCounter(( + cC, wC, pC = standardCounter( "Some text\n\n" "[NEWPAGE]\n\n" "more text\n\n" @@ -150,13 +150,13 @@ def testTextCounting_standardCounter(): "and some final text\n\n" "[VSPACE:4]\n\n" "THE END\n\n" - )) + ) assert cC == 58 assert wC == 13 assert pC == 5 # Formatting Codes, Lower Case (Current Implementation) - cC, wC, pC = standardCounter(( + cC, wC, pC = standardCounter( "Some text\n\n" "[newpage]\n\n" "more text\n\n" @@ -166,16 +166,16 @@ def testTextCounting_standardCounter(): "and some final text\n\n" "[vspace:4]\n\n" "THE END\n\n" - )) + ) assert cC == 58 assert wC == 13 assert pC == 5 # Check ShortCodes - cC, wC, pC = standardCounter(( + cC, wC, pC = standardCounter( "Text with [b]bold[/b] text and padded [b] bold [/b] text.\n\n" "Text with [b][i] nested [/i] emphasis [/b] in it.\n\n" - )) + ) assert cC == 78 assert wC == 14 assert pC == 2 @@ -188,7 +188,7 @@ def testTextCounting_bodyTextCounter(): assert bodyTextCounter(None) == (0, 0, 0) # type: ignore # General Text - wC, cC, sC = bodyTextCounter(( + wC, cC, sC = bodyTextCounter( "#! Title\n\n" "##! Prologue\n\n" "# Heading One\n" @@ -201,7 +201,7 @@ def testTextCounting_bodyTextCounter(): "The second paragraph.\n\n\n" "The third paragraph.\n\n" "Dashes\u2013and even longer\u2014dashes." - )) + ) assert wC == 14 assert cC == 91 assert sC == 81 diff --git a/tests/test_tools/test_tools_manuscript.py b/tests/test_tools/test_tools_manuscript.py index 4e7959cb..070b5db6 100644 --- a/tests/test_tools/test_tools_manuscript.py +++ b/tests/test_tools/test_tools_manuscript.py @@ -293,7 +293,7 @@ def testToolManuscript_Features(monkeypatch, qtbot, nwGUI, projPath, mockRnd): obj.close() break else: - assert False + raise AssertionError # Finish manus.close() @@ -327,7 +327,7 @@ def testToolManuscript_Print(monkeypatch, qtbot, nwGUI, projPath): obj.close() break else: - assert False + raise AssertionError # Finish manus.close() diff --git a/tests/test_tools/test_tools_writingstats.py b/tests/test_tools/test_tools_writingstats.py index 53475726..298b8540 100644 --- a/tests/test_tools/test_tools_writingstats.py +++ b/tests/test_tools/test_tools_writingstats.py @@ -121,41 +121,41 @@ def testToolWritingStats_Export(qtbot, monkeypatch, nwGUI, projPath, tstPaths): # Sort by time sessLog.listBox.sortByColumn(sessLog.C_TIME, Qt.SortOrder.AscendingOrder) - assert sessLog.novelWords.text() == "{:n}".format(600) - assert sessLog.notesWords.text() == "{:n}".format(275) - assert sessLog.totalWords.text() == "{:n}".format(875) + assert sessLog.novelWords.text() == f"{600:n}" + assert sessLog.notesWords.text() == f"{275:n}" + assert sessLog.totalWords.text() == f"{875:n}" item = sessLog.listBox.topLevelItem(0) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(1) + assert item.text(sessLog.C_COUNT) == f"{1:n}" item = sessLog.listBox.topLevelItem(1) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) + assert item.text(sessLog.C_COUNT) == f"{-200:n}" item = sessLog.listBox.topLevelItem(2) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(300) + assert item.text(sessLog.C_COUNT) == f"{300:n}" item = sessLog.listBox.topLevelItem(3) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-120) + assert item.text(sessLog.C_COUNT) == f"{-120:n}" item = sessLog.listBox.topLevelItem(4) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-20) + assert item.text(sessLog.C_COUNT) == f"{-20:n}" item = sessLog.listBox.topLevelItem(5) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(40) + assert item.text(sessLog.C_COUNT) == f"{40:n}" item = sessLog.listBox.topLevelItem(6) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-400) + assert item.text(sessLog.C_COUNT) == f"{-400:n}" item = sessLog.listBox.topLevelItem(7) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(200) + assert item.text(sessLog.C_COUNT) == f"{200:n}" assert sessLog._saveData(sessLog.FMT_CSV) assert sessLog._saveData(sessLog.FMT_JSON) @@ -230,35 +230,35 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths): item = sessLog.listBox.topLevelItem(0) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(1) + assert item.text(sessLog.C_COUNT) == f"{1:n}" item = sessLog.listBox.topLevelItem(1) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) + assert item.text(sessLog.C_COUNT) == f"{-200:n}" item = sessLog.listBox.topLevelItem(2) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(300) + assert item.text(sessLog.C_COUNT) == f"{300:n}" item = sessLog.listBox.topLevelItem(3) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-120) + assert item.text(sessLog.C_COUNT) == f"{-120:n}" item = sessLog.listBox.topLevelItem(4) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-20) + assert item.text(sessLog.C_COUNT) == f"{-20:n}" item = sessLog.listBox.topLevelItem(5) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(40) + assert item.text(sessLog.C_COUNT) == f"{40:n}" item = sessLog.listBox.topLevelItem(6) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-400) + assert item.text(sessLog.C_COUNT) == f"{-400:n}" item = sessLog.listBox.topLevelItem(7) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(200) + assert item.text(sessLog.C_COUNT) == f"{200:n}" # No Novel Files qtbot.mouseClick(sessLog.incNovel, QtMouseLeft) @@ -270,35 +270,35 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths): item = sessLog.listBox.topLevelItem(0) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(1) + assert item.text(sessLog.C_COUNT) == f"{1:n}" item = sessLog.listBox.topLevelItem(1) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-100) + assert item.text(sessLog.C_COUNT) == f"{-100:n}" item = sessLog.listBox.topLevelItem(2) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(150) + assert item.text(sessLog.C_COUNT) == f"{150:n}" item = sessLog.listBox.topLevelItem(3) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-60) + assert item.text(sessLog.C_COUNT) == f"{-60:n}" item = sessLog.listBox.topLevelItem(4) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-10) + assert item.text(sessLog.C_COUNT) == f"{-10:n}" item = sessLog.listBox.topLevelItem(5) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(20) + assert item.text(sessLog.C_COUNT) == f"{20:n}" item = sessLog.listBox.topLevelItem(6) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) + assert item.text(sessLog.C_COUNT) == f"{-200:n}" item = sessLog.listBox.topLevelItem(7) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(100) + assert item.text(sessLog.C_COUNT) == f"{100:n}" assert jsonData == [ { @@ -339,35 +339,35 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths): item = sessLog.listBox.topLevelItem(0) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(1) + assert item.text(sessLog.C_COUNT) == f"{1:n}" item = sessLog.listBox.topLevelItem(1) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-100) + assert item.text(sessLog.C_COUNT) == f"{-100:n}" item = sessLog.listBox.topLevelItem(2) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(150) + assert item.text(sessLog.C_COUNT) == f"{150:n}" item = sessLog.listBox.topLevelItem(3) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-60) + assert item.text(sessLog.C_COUNT) == f"{-60:n}" item = sessLog.listBox.topLevelItem(4) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-10) + assert item.text(sessLog.C_COUNT) == f"{-10:n}" item = sessLog.listBox.topLevelItem(5) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(20) + assert item.text(sessLog.C_COUNT) == f"{20:n}" item = sessLog.listBox.topLevelItem(6) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) + assert item.text(sessLog.C_COUNT) == f"{-200:n}" item = sessLog.listBox.topLevelItem(7) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(100) + assert item.text(sessLog.C_COUNT) == f"{100:n}" assert jsonData == [ { @@ -408,19 +408,19 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths): item = sessLog.listBox.topLevelItem(0) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(1) + assert item.text(sessLog.C_COUNT) == f"{1:n}" item = sessLog.listBox.topLevelItem(1) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(300) + assert item.text(sessLog.C_COUNT) == f"{300:n}" item = sessLog.listBox.topLevelItem(2) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(40) + assert item.text(sessLog.C_COUNT) == f"{40:n}" item = sessLog.listBox.topLevelItem(3) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(200) + assert item.text(sessLog.C_COUNT) == f"{200:n}" assert jsonData == [ { @@ -449,43 +449,43 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths): item = sessLog.listBox.topLevelItem(0) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(1) + assert item.text(sessLog.C_COUNT) == f"{1:n}" item = sessLog.listBox.topLevelItem(1) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(0) + assert item.text(sessLog.C_COUNT) == f"{0:n}" item = sessLog.listBox.topLevelItem(2) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) + assert item.text(sessLog.C_COUNT) == f"{-200:n}" item = sessLog.listBox.topLevelItem(3) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(300) + assert item.text(sessLog.C_COUNT) == f"{300:n}" item = sessLog.listBox.topLevelItem(4) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-120) + assert item.text(sessLog.C_COUNT) == f"{-120:n}" item = sessLog.listBox.topLevelItem(5) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-20) + assert item.text(sessLog.C_COUNT) == f"{-20:n}" item = sessLog.listBox.topLevelItem(6) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(40) + assert item.text(sessLog.C_COUNT) == f"{40:n}" item = sessLog.listBox.topLevelItem(7) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-400) + assert item.text(sessLog.C_COUNT) == f"{-400:n}" item = sessLog.listBox.topLevelItem(8) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(200) + assert item.text(sessLog.C_COUNT) == f"{200:n}" item = sessLog.listBox.topLevelItem(9) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(0) + assert item.text(sessLog.C_COUNT) == f"{0:n}" assert jsonData == [ { @@ -542,35 +542,35 @@ def testToolWritingStats_Filters(qtbot, monkeypatch, nwGUI, projPath, tstPaths): item = sessLog.listBox.topLevelItem(0) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(1) + assert item.text(sessLog.C_COUNT) == f"{1:n}" item = sessLog.listBox.topLevelItem(1) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-200) + assert item.text(sessLog.C_COUNT) == f"{-200:n}" item = sessLog.listBox.topLevelItem(2) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(180) + assert item.text(sessLog.C_COUNT) == f"{180:n}" item = sessLog.listBox.topLevelItem(3) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-20) + assert item.text(sessLog.C_COUNT) == f"{-20:n}" item = sessLog.listBox.topLevelItem(4) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(40) + assert item.text(sessLog.C_COUNT) == f"{40:n}" item = sessLog.listBox.topLevelItem(5) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(-400) + assert item.text(sessLog.C_COUNT) == f"{-400:n}" item = sessLog.listBox.topLevelItem(6) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(200) + assert item.text(sessLog.C_COUNT) == f"{200:n}" item = sessLog.listBox.topLevelItem(7) assert item is not None - assert item.text(sessLog.C_COUNT) == "{:n}".format(0) + assert item.text(sessLog.C_COUNT) == f"{0:n}" assert jsonData == [ { diff --git a/tests/tools.py b/tests/tools.py index 7695ae72..7acc598f 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -97,18 +97,18 @@ def cmpFiles( lnTwo = txtTwo[n].strip() if n+1 in ignoreLines: - print("Ignoring line %d" % (n+1)) + print(f"Ignoring line {n+1}") continue if ignoreStart is not None: if lnOne.startswith(ignoreStart): - print("Ignoring line %d" % (n+1)) + print(f"Ignoring line {n+1}") continue if lnOne != lnTwo: - print("Diff on line %d:" % (n+1)) - print(" << '%s'" % lnOne) - print(" >> '%s'" % lnTwo) + print(f"Diff on line {n+1}:") + print(f" << '{lnOne}'") + print(f" >> '{lnTwo}'") diffFound = True return not diffFound @@ -205,11 +205,11 @@ def buildTestProject(obj: object, projPath: Path) -> None: project.index.reIndexHandle(tdHandle) aDoc = project.storage.getDocument(cdHandle) - aDoc.writeDocument("## %s\n\n" % project.tr("New Chapter")) + aDoc.writeDocument("## {0}\n\n".format(project.tr("New Chapter"))) project.index.reIndexHandle(cdHandle) aDoc = project.storage.getDocument(sdHandle) - aDoc.writeDocument("### %s\n\n" % project.tr("New Scene")) + aDoc.writeDocument("### {0}\n\n".format(project.tr("New Scene"))) project.index.reIndexHandle(sdHandle) project.session.startSession() diff --git a/utils/assets.py b/utils/assets.py index 393b3838..dc53b8b3 100644 --- a/utils/assets.py +++ b/utils/assets.py @@ -102,7 +102,7 @@ def buildSampleZip(args: argparse.Namespace | None = None) -> None: sys.exit(1) print("") - print("Built file: %s" % dstSample) + print(f"Built file: {dstSample}") print("") return @@ -242,7 +242,7 @@ def buildTranslationAssets(args: argparse.Namespace | None = None) -> None: for item in srcDir.iterdir(): if item.is_file() and item.suffix == ".qm": item.rename(dstDir / item.name) - print("Moved: %s -> %s" % (item.relative_to(ROOT_DIR), dstRel / item.name)) + print(f"Moved: {item.relative_to(ROOT_DIR)} -> {dstRel / item.name}") print("") diff --git a/utils/build_windows.py b/utils/build_windows.py index c34bfdd8..89766be0 100644 --- a/utils/build_windows.py +++ b/utils/build_windows.py @@ -65,12 +65,12 @@ def embedPython(bldDir: Path, outDir: Path) -> None: """Embed Python library.""" print("Adding Python embeddable ...") - pyVers = "%d.%d.%d" % (sys.version_info[:3]) + pyVers = ".".join(str(v) for v in sys.version_info[:3]) zipFile = f"python-{pyVers}-embed-amd64.zip" pyZip = bldDir / zipFile if not pyZip.is_file(): pyUrl = f"https://www.python.org/ftp/python/{pyVers}/{zipFile}" - print("Downloading: %s" % pyUrl) + print(f"Downloading: {pyUrl}") urllib.request.urlretrieve(pyUrl, pyZip) print("Extracting ...") @@ -192,7 +192,7 @@ def main(args: argparse.Namespace) -> None: print("") numVers, _, _ = extractVersion() - print("Version: %s" % numVers) + print(f"Version: {numVers}") bldDir = ROOT_DIR / "dist" outDir = bldDir / "novelWriter" diff --git a/utils/common.py b/utils/common.py index e5b85613..c5d88469 100644 --- a/utils/common.py +++ b/utils/common.py @@ -44,17 +44,17 @@ def extractVersion(beQuiet: bool = False) -> tuple[str, str, str]: try: for aLine in initFile.read_text(encoding="utf-8").splitlines(): if aLine.startswith("__version__"): - numVers = getValue((aLine)) + numVers = getValue(aLine) if aLine.startswith("__hexversion__"): - hexVers = getValue((aLine)) + hexVers = getValue(aLine) if aLine.startswith("__date__"): - relDate = getValue((aLine)) + relDate = getValue(aLine) except Exception as exc: - print("Could not read file: %s" % initFile) + print(f"Could not read file: {initFile}") print(str(exc)) if not beQuiet: - print("novelWriter version: %s (%s) at %s" % (numVers, hexVers, relDate)) + print(f"novelWriter version: {numVers} ({hexVers}) at {relDate}") return numVers, hexVers, relDate @@ -95,7 +95,7 @@ def copyPackageFiles(dst: Path, setupPy: bool = False) -> None: copyFiles = ["LICENSE.md", "CREDITS.md", "pyproject.toml"] for copyFile in copyFiles: shutil.copyfile(copyFile, dst / copyFile) - print("Copied: %s" % copyFile) + print(f"Copied: {copyFile}") writeFile(dst / "MANIFEST.in", ( "include LICENSE.md\n" diff --git a/utils/icon_themes.py b/utils/icon_themes.py index 54044bf7..00d75714 100644 --- a/utils/icon_themes.py +++ b/utils/icon_themes.py @@ -165,7 +165,7 @@ def _fixXml(svg: ET.Element) -> str: def _writeThemeFile( - path: Path, name: str, author: str, license: str, icons: dict[str, ET.Element] + path: Path, name: str, author: str, license_: str, icons: dict[str, ET.Element] ) -> None: """Write an icon theme file.""" with open(path.with_suffix(".icons"), mode="w", encoding="utf-8") as out: @@ -173,7 +173,7 @@ def _writeThemeFile( out.write("# Meta\n") out.write(f"meta:name = {name}\n") out.write(f"meta:author = {author}\n") - out.write(f"meta:license = {license}\n") + out.write(f"meta:license = {license_}\n") out.write("\n") out.write("# Icons\n") for key, svg in icons.items(): From 6a66e06544f90d8614fa2ac33f3888c769f44aa2 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 3 Apr 2025 01:27:55 +0200 Subject: [PATCH 3/3] Add a couple more rules --- novelwriter/core/index.py | 2 +- pyproject.toml | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index f3178faf..b4bdaba5 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -777,7 +777,7 @@ class TagsIndex: control of the keys. """ - __slots__ = ("_tags") + __slots__ = ("_tags",) def __init__(self) -> None: self._tags: dict[str, dict[str, str]] = {} diff --git a/pyproject.toml b/pyproject.toml index b8073e0f..d22d9bd3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,25 +60,29 @@ line-length = 99 [tool.ruff.lint] select = [ - "A", # flake8-builtins (A) - "B", # flake8-bugbear (B) - "E", # pycodestyle (E) - "F", # Pyflakes (F) - "W", # pycodestyle (W) - "UP", + "A", # flake8-builtins (A) + "B", # flake8-bugbear (B) + "E", # pycodestyle (E) + "F", # Pyflakes (F) + "W", # pycodestyle (W) + "UP", # pyupgrade (UP) + "ANN", # flake8-annotations (ANN) + "PLC", # Pylint Convention (PLC) + "PLE", # Pylint Error (PLE) + "FA", # flake8-future-annotations (FA) ] ignore = [ "E221", # multiple-spaces-before-operator "E226", # missing-whitespace-around-arithmetic-operator "E228", # missing-whitespace-around-modulo-operator "E241", # multiple-spaces-after-comma - "ANN401", + "ANN401", # any-type "UP015", # redundant-open-modes "UP030", # format-literals ] [tool.ruff.lint.per-file-ignores] -"tests/*" = [] +"tests/*" = ["ANN"] [tool.ruff.format] quote-style = "double"