Error logging and mutables as function defaults (#930)

* Remove mutable iterables as function defaults
* Improve error reporting and standardise how it's done
This commit is contained in:
Veronica Berglyd Olsen
2021-11-10 00:32:01 +01:00
committed by GitHub
parent 67d2e266fd
commit 00eea24e3f
15 changed files with 88 additions and 65 deletions
+7 -7
View File
@@ -36,7 +36,7 @@ from PyQt5.QtCore import (
QTranslator
)
from novelwriter.error import logException
from novelwriter.error import logException, formatException
from novelwriter.common import splitVersionNumber, formatTimeStamp, NWConfigParser
from novelwriter.constants import nwFiles, nwUnicode
@@ -312,7 +312,7 @@ class Config:
logException()
self.hasError = True
self.errData.append("Could not create folder: %s" % self.confPath)
self.errData.append(str(exc))
self.errData.append(formatException(exc))
self.confPath = None
# Check if config file exists
@@ -335,7 +335,7 @@ class Config:
logException()
self.hasError = True
self.errData.append("Could not create folder: %s" % self.dataPath)
self.errData.append(str(exc))
self.errData.append(formatException(exc))
self.dataPath = None
# Host and Kernel
@@ -430,7 +430,7 @@ class Config:
logException()
self.hasError = True
self.errData.append("Could not load config file")
self.errData.append(str(exc))
self.errData.append(formatException(exc))
return False
# Main
@@ -660,7 +660,7 @@ class Config:
logException()
self.hasError = True
self.errData.append("Could not save config file")
self.errData.append(str(exc))
self.errData.append(formatException(exc))
return False
return True
@@ -691,7 +691,7 @@ class Config:
except Exception as exc:
self.hasError = True
self.errData.append("Could not load recent project cache")
self.errData.append(str(exc))
self.errData.append(formatException(exc))
return False
return True
@@ -711,7 +711,7 @@ class Config:
except Exception as exc:
self.hasError = True
self.errData.append("Could not save recent project cache")
self.errData.append(str(exc))
self.errData.append(formatException(exc))
return False
if os.path.isfile(cacheFile):
+5 -4
View File
@@ -27,6 +27,7 @@ import os
import logging
from novelwriter.enum import nwItemLayout, nwItemClass
from novelwriter.error import formatException
from novelwriter.common import isHandle, sha256sum
logger = logging.getLogger(__name__)
@@ -101,7 +102,7 @@ class NWDoc():
theText += inFile.read()
except Exception as exc:
self._docError = str(exc)
self._docError = formatException(exc)
return None
else:
@@ -150,7 +151,7 @@ class NWDoc():
outFile.write(docMeta)
outFile.write(docText)
except Exception as exc:
self._docError = str(exc)
self._docError = formatException(exc)
return False
# If we're here, the file was successfully saved, so we can
@@ -185,7 +186,7 @@ class NWDoc():
os.unlink(chkFile)
logger.debug("Deleted: %s", chkFile)
except Exception as exc:
self._docError = str(exc)
self._docError = formatException(exc)
return False
return True
@@ -225,7 +226,7 @@ class NWDoc():
##
def _parseMeta(self, metaLine):
"""Parse a line from the document statting with the characters
"""Parse a line from the document starting with the characters
%%~ that may contain meta data.
"""
if metaLine.startswith("%%~name:"):
+4 -3
View File
@@ -32,6 +32,7 @@ import novelwriter
from time import time
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
from novelwriter.error import logException
from novelwriter.constants import nwFiles, nwKeyWords, nwUnicode
from novelwriter.core.document import NWDoc
from novelwriter.common import (
@@ -155,7 +156,7 @@ class NWIndex():
except Exception:
logger.error("Failed to load index file")
novelwriter.logException()
logException()
self.indexBroken = True
return False
@@ -194,7 +195,7 @@ class NWIndex():
except Exception:
logger.error("Failed to save index file")
novelwriter.logException()
logException()
return False
logger.verbose("Index saved in %.3f ms", (time() - tStart)*1000)
@@ -217,7 +218,7 @@ class NWIndex():
except Exception:
logger.error("Error while checking index")
novelwriter.logException()
logException()
self.indexBroken = True
logger.verbose("Index check took %.3f ms", (time() - tStart)*1000)
+4 -4
View File
@@ -27,10 +27,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import os
import json
import logging
import novelwriter
from novelwriter.constants import nwFiles
from novelwriter.error import logException
from novelwriter.common import checkBool, checkFloat, checkInt, checkString
from novelwriter.constants import nwFiles
logger = logging.getLogger(__name__)
@@ -86,7 +86,7 @@ class OptionState():
theState = json.load(inFile)
except Exception:
logger.error("Failed to load GUI options file")
novelwriter.logException()
logException()
return False
# Filter out unused variables
@@ -113,7 +113,7 @@ class OptionState():
json.dump(self._theState, outFile, indent=2)
except Exception:
logger.error("Failed to save GUI options file")
novelwriter.logException()
logException()
return False
return True
+17 -13
View File
@@ -37,14 +37,15 @@ from PyQt5.QtCore import QCoreApplication
from novelwriter.core.tree import NWTree
from novelwriter.core.item import NWItem
from novelwriter.core.document import NWDoc
from novelwriter.core.status import NWStatus
from novelwriter.core.options import OptionState
from novelwriter.core.document import NWDoc
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
from novelwriter.error import logException
from novelwriter.common import (
checkString, checkBool, checkInt, isHandle, formatTimeStamp,
makeFileNameSafe, hexToInt
)
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
from novelwriter.constants import trConst, nwFiles, nwLabels
logger = logging.getLogger(__name__)
@@ -236,10 +237,13 @@ class NWProject():
return
def newProject(self, projData={}):
def newProject(self, projData=None):
"""Create a new project by populating the project tree with a
few starter items.
"""
if projData is None:
projData = {}
popMinimal = projData.get("popMinimal", True)
popCustom = projData.get("popCustom", False)
popSample = projData.get("popSample", False)
@@ -1252,7 +1256,7 @@ class NWProject():
except Exception:
logger.error("Failed to project language file")
novelwriter.logException()
logException()
return False
return True
@@ -1277,7 +1281,7 @@ class NWProject():
except Exception:
logger.error("Failed to read project lockfile")
novelwriter.logException()
logException()
return ["ERROR"]
return theLines
@@ -1298,7 +1302,7 @@ class NWProject():
except Exception:
logger.error("Failed to write project lockfile")
novelwriter.logException()
logException()
return False
return True
@@ -1315,7 +1319,7 @@ class NWProject():
os.unlink(lockFile)
except Exception:
logger.error("Failed to remove project lockfile")
novelwriter.logException()
logException()
return False
return True
@@ -1488,7 +1492,7 @@ class NWProject():
except Exception:
logger.error("Failed to write session stats file")
novelwriter.logException()
logException()
return False
return True
@@ -1526,7 +1530,7 @@ class NWProject():
except Exception:
errList.append(self.tr("Could not move: {0}").format(theFile))
logger.error("Could not move: %s", theFile)
novelwriter.logException()
logException()
elif len(dataItem) == 21 and dataItem.endswith("_main.bak"):
try:
@@ -1535,7 +1539,7 @@ class NWProject():
except Exception:
errList.append(self.tr("Could not delete: {0}").format(theFile))
logger.error("Could not delete: %s", theFile)
novelwriter.logException()
logException()
else:
theErr = self._moveUnknownItem(theData, dataItem)
@@ -1549,7 +1553,7 @@ class NWProject():
except Exception:
errList.append(self.tr("Could not delete: {0}").format(theFolder))
logger.error("Could not delete: %s", theFolder)
novelwriter.logException()
logException()
return errList
@@ -1569,7 +1573,7 @@ class NWProject():
logger.info("Moved to junk: %s", theSrc)
except Exception:
logger.error("Could not move item %s to junk", theSrc)
novelwriter.logException()
logException()
return self.tr("Could not move item {0} to {1}.").format(theSrc, theJunk)
return ""
@@ -1604,7 +1608,7 @@ class NWProject():
os.unlink(rmFile)
except Exception:
logger.error("Could not delete: %s", rmFile)
novelwriter.logException()
logException()
return False
return True
+5 -3
View File
@@ -27,6 +27,8 @@ import os
import logging
import novelwriter
from novelwriter.error import logException
logger = logging.getLogger(__name__)
@@ -107,7 +109,7 @@ class NWSpellEnchant():
self._projDict.add(newWord)
except Exception:
logger.error("Failed to add word to project word list %s", str(self._projectDict))
novelwriter.logException()
logException()
return False
return True
@@ -135,7 +137,7 @@ class NWSpellEnchant():
spName = self._theDict.provider.name
except Exception:
logger.error("Failed to extract information about the dictionary")
novelwriter.logException()
logException()
spTag = ""
spName = ""
@@ -169,7 +171,7 @@ class NWSpellEnchant():
except Exception:
logger.error("Failed to load project word list")
novelwriter.logException()
logException()
return False
return True
+2 -2
View File
@@ -25,13 +25,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import os
import logging
import novelwriter
from time import time
from lxml import etree
from hashlib import sha256
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
from novelwriter.error import logException
from novelwriter.common import checkHandle
from novelwriter.constants import nwConst, nwFiles
from novelwriter.core.item import NWItem
@@ -184,7 +184,7 @@ class NWTree():
except Exception:
logger.error("Could not write ToC file")
novelwriter.logException()
logException()
return False
return True
+2 -1
View File
@@ -34,6 +34,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter.enum import nwAlert
from novelwriter.error import logException
from novelwriter.constants import nwFiles
logger = logging.getLogger(__name__)
@@ -161,7 +162,7 @@ class GuiWordList(QDialog):
except Exception:
logger.error("Could not save new word list")
novelwriter.logException()
logException()
self.reject()
return False
+10 -3
View File
@@ -43,7 +43,14 @@ def logException():
"""Log the content of an exception message.
"""
exType, exValue, _ = sys.exc_info()
logger.error("%s: %s", exType.__name__, str(exValue).strip("'"))
logger.error("%s: %s", exType.__name__, str(exValue))
def formatException(exc):
"""Format an exception as a string the same way the default
exception handler does.
"""
return f"{type(exc).__name__}: {str(exc)}"
# =============================================================================================== #
@@ -185,11 +192,11 @@ def exceptionHandler(exType, exValue, exTrace):
except Exception as exc:
logger.critical("Could not close the project before exiting")
logger.critical(str(exc))
logger.critical(formatException(exc))
qApp.exit(1)
except Exception as exc:
logger.critical(str(exc))
logger.critical(formatException(exc))
return
+2 -1
View File
@@ -41,6 +41,7 @@ from PyQt5.QtWidgets import (
from novelwriter.core import ToHtml
from novelwriter.enum import nwAlert, nwItemType, nwDocAction
from novelwriter.error import logException
from novelwriter.constants import nwUnicode
logger = logging.getLogger(__name__)
@@ -185,7 +186,7 @@ class GuiDocViewer(QTextBrowser):
aDoc.doPostProcessing()
except Exception:
logger.error("Failed to generate preview for document with handle '%s'", tHandle)
novelwriter.logException()
logException()
self.setText(self.tr("An error occurred while generating the preview."))
return False
+5 -4
View File
@@ -37,6 +37,7 @@ from PyQt5.QtGui import (
)
from novelwriter.enum import nwItemLayout, nwItemType
from novelwriter.error import logException
from novelwriter.common import NWConfigParser, readTextFile
from novelwriter.constants import nwLabels
@@ -250,7 +251,7 @@ class GuiTheme:
confParser.read_file(inFile)
except Exception:
logger.error("Could not load theme settings from: %s", self.themeFile)
novelwriter.logException()
logException()
return False
# Main
@@ -310,7 +311,7 @@ class GuiTheme:
confParser.read_file(inFile)
except Exception:
logger.error("Could not load syntax colours from: %s", self.syntaxFile)
novelwriter.logException()
logException()
return False
# Main
@@ -531,7 +532,7 @@ class GuiIcons:
confParser.read_file(inFile)
except Exception:
logger.error("Could not load icon theme settings from: %s", themeConf)
novelwriter.logException()
logException()
return False
# Main
@@ -729,7 +730,7 @@ def _loadInternalName(confParser, confFile):
confParser.read_file(inFile)
except Exception:
logger.error("Could not load file: %s", confFile)
novelwriter.logException()
logException()
return ""
return confParser.rdStr("Main", "name", "")
+15 -11
View File
@@ -45,6 +45,7 @@ from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
from novelwriter.core import ToHtml, ToOdt, ToMarkdown
from novelwriter.enum import nwAlert, nwItemType, nwItemLayout, nwItemClass
from novelwriter.error import formatException, logException
from novelwriter.common import fuzzyTime, makeFileNameSafe
from novelwriter.constants import nwConst, nwFiles
from novelwriter.gui.custom import QSwitch
@@ -727,7 +728,7 @@ class GuiBuildNovel(QDialog):
except Exception:
logger.error("Failed to build document '%s'", tItem.itemHandle)
novelwriter.logException()
logException()
if isPreview:
self.docView.setText((
"Failed to generate preview. "
@@ -873,7 +874,7 @@ class GuiBuildNovel(QDialog):
makeOdt.saveOpenDocText(savePath)
wSuccess = True
except Exception as exc:
errMsg = str(exc)
errMsg = formatException(exc)
elif theFmt == self.FMT_FODT:
makeOdt = ToOdt(self.theProject, isFlat=True)
@@ -882,7 +883,7 @@ class GuiBuildNovel(QDialog):
makeOdt.saveFlatXML(savePath)
wSuccess = True
except Exception as exc:
errMsg = str(exc)
errMsg = formatException(exc)
elif theFmt == self.FMT_HTM:
makeHtml = ToHtml(self.theProject)
@@ -894,7 +895,7 @@ class GuiBuildNovel(QDialog):
makeHtml.saveHTML5(savePath)
wSuccess = True
except Exception as exc:
errMsg = str(exc)
errMsg = formatException(exc)
elif theFmt == self.FMT_NWD:
makeNwd = ToMarkdown(self.theProject)
@@ -907,7 +908,7 @@ class GuiBuildNovel(QDialog):
makeNwd.saveRawMarkdown(savePath)
wSuccess = True
except Exception as exc:
errMsg = str(exc)
errMsg = formatException(exc)
elif theFmt in (self.FMT_MD, self.FMT_GH):
makeMd = ToMarkdown(self.theProject)
@@ -924,7 +925,7 @@ class GuiBuildNovel(QDialog):
makeMd.saveMarkdown(savePath)
wSuccess = True
except Exception as exc:
errMsg = str(exc)
errMsg = formatException(exc)
elif theFmt == self.FMT_JSON_H or theFmt == self.FMT_JSON_M:
jsonData = {
@@ -969,7 +970,7 @@ class GuiBuildNovel(QDialog):
outFile.write(json.dumps(jsonData, indent=2))
wSuccess = True
except Exception as exc:
errMsg = str(exc)
errMsg = formatException(exc)
elif theFmt == self.FMT_PDF:
try:
@@ -984,7 +985,7 @@ class GuiBuildNovel(QDialog):
wSuccess = True
except Exception as exc:
errMsg = str(exc)
errMsg = formatException(exc)
else:
# If the if statements above and here match, it should not
@@ -1050,7 +1051,7 @@ class GuiBuildNovel(QDialog):
theData = json.loads(theJson)
except Exception:
logger.error("Failed to load build cache")
novelwriter.logException()
logException()
return False
if "buildTime" in theData.keys():
@@ -1078,7 +1079,7 @@ class GuiBuildNovel(QDialog):
}, indent=2))
except Exception:
logger.error("Failed to save build cache")
novelwriter.logException()
logException()
return False
return True
@@ -1302,9 +1303,12 @@ class GuiBuildNovelDocView(QTextBrowser):
return
def setStyleSheet(self, theStyles=[]):
def setStyleSheet(self, theStyles=None):
"""Set the stylesheet for the preview document.
"""
if theStyles is None:
theStyles = []
if not theStyles:
theStyles.append("h1, h2 {color: rgb(66, 113, 174);}")
theStyles.append("h3, h4 {color: rgb(50, 50, 50);}")
+5 -4
View File
@@ -38,6 +38,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter.enum import nwAlert
from novelwriter.error import formatException
from novelwriter.common import formatTime, checkInt, checkIntRange, checkIntTuple
from novelwriter.constants import nwConst, nwFiles
from novelwriter.gui.custom import QSwitch
@@ -404,7 +405,7 @@ class GuiWritingStats(QDialog):
wSuccess = True
except Exception as exc:
errMsg = str(exc)
errMsg = formatException(exc)
wSuccess = False
# Report to user
@@ -480,9 +481,9 @@ class GuiWritingStats(QDialog):
self.logData.append((dStart, sDiff, wcNovel, wcNotes, sIdle))
except Exception as exc:
self.theParent.makeAlert([
self.tr("Failed to read session log file."), str(exc)
], nwAlert.ERROR)
self.theParent.makeAlert(self.tr(
"Failed to read session log file."
), nwAlert.ERROR, exception=exc)
return False
ttWords = ttNovel + ttNotes
+2 -2
View File
@@ -113,8 +113,8 @@ class MockApp:
# =========================================================================== #
def causeOSError(*args, **kwargs):
raise OSError("OSError")
raise OSError("Mock OSError")
def causeException(*args, **kwargs):
raise Exception("Exception")
raise Exception("Mock Exception")
+3 -3
View File
@@ -56,7 +56,7 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal):
mp.setattr("builtins.open", causeOSError)
theDoc = NWDoc(theProject, sHandle)
assert theDoc.readDocument() is None
assert theDoc.getError() == "OSError"
assert theDoc.getError() == "OSError: Mock OSError"
# Load the text
theDoc = NWDoc(theProject, sHandle)
@@ -107,7 +107,7 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal):
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert theDoc.writeDocument(theText) is False
assert theDoc.getError() == "OSError"
assert theDoc.getError() == "OSError: Mock OSError"
# Saving with no handle
theDoc._docHandle = None
@@ -126,7 +126,7 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal):
mp.setattr("os.unlink", causeOSError)
theDoc = NWDoc(theProject, xHandle)
assert theDoc.deleteDocument() is False
assert theDoc.getError() == "OSError"
assert theDoc.getError() == "OSError: Mock OSError"
# Make the delete pass
theDoc = NWDoc(theProject, xHandle)