Fix some typing issues
This commit is contained in:
+50
-58
@@ -29,13 +29,13 @@ import hashlib
|
||||
import logging
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from typing import Any
|
||||
from typing import Any, Literal
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from configparser import ConfigParser
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
from PyQt5.QtWidgets import qApp
|
||||
from PyQt5.QtWidgets import QWidget, qApp
|
||||
|
||||
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
|
||||
from novelwriter.error import logException
|
||||
@@ -113,14 +113,14 @@ def checkHandle(value, default, allowNone=False):
|
||||
|
||||
|
||||
def checkUuid(value: Any, default: str) -> str:
|
||||
"""Try to process a value as an uuid, or return a default."""
|
||||
"""Try to process a value as an UUID, or return a default."""
|
||||
try:
|
||||
return str(uuid.UUID(value))
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
|
||||
def checkPath(value, default):
|
||||
def checkPath(value: Any, default: Path) -> Path:
|
||||
"""Check if a value is a valid path. Non-empty strings are accepted.
|
||||
"""
|
||||
if isinstance(value, Path):
|
||||
@@ -135,7 +135,7 @@ def checkPath(value, default):
|
||||
# Validator Functions
|
||||
# =============================================================================================== #
|
||||
|
||||
def isHandle(value):
|
||||
def isHandle(value: Any) -> bool:
|
||||
"""Check if a string is a valid novelWriter handle.
|
||||
Note: This is case sensitive. Must be lower case!
|
||||
"""
|
||||
@@ -149,9 +149,8 @@ def isHandle(value):
|
||||
return True
|
||||
|
||||
|
||||
def isTitleTag(value):
|
||||
"""Check if a string is a valid title tag string.
|
||||
"""
|
||||
def isTitleTag(value: Any) -> bool:
|
||||
"""Check if a string is a valid title tag string."""
|
||||
if not isinstance(value, str):
|
||||
return False
|
||||
if len(value) != 5:
|
||||
@@ -164,27 +163,23 @@ def isTitleTag(value):
|
||||
return True
|
||||
|
||||
|
||||
def isItemClass(value):
|
||||
"""Check if a string is a valid nwItemClass identifier.
|
||||
"""
|
||||
def isItemClass(value: str) -> bool:
|
||||
"""Check if a string is a valid nwItemClass identifier."""
|
||||
return value in nwItemClass.__members__
|
||||
|
||||
|
||||
def isItemType(value):
|
||||
"""Check if a string is a valid nwItemType identifier.
|
||||
"""
|
||||
def isItemType(value: str) -> bool:
|
||||
"""Check if a string is a valid nwItemType identifier."""
|
||||
return value in nwItemType.__members__
|
||||
|
||||
|
||||
def isItemLayout(value):
|
||||
"""Check if a string is a valid nwItemLayout identifier.
|
||||
"""
|
||||
def isItemLayout(value: str) -> bool:
|
||||
"""Check if a string is a valid nwItemLayout identifier."""
|
||||
return value in nwItemLayout.__members__
|
||||
|
||||
|
||||
def hexToInt(value, default=0):
|
||||
"""Convert a hex string to an integer.
|
||||
"""
|
||||
def hexToInt(value: Any, default: int = 0) -> int:
|
||||
"""Convert a hex string to an integer."""
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
return int(value, 16)
|
||||
@@ -193,13 +188,13 @@ def hexToInt(value, default=0):
|
||||
return default
|
||||
|
||||
|
||||
def minmax(value, minVal, maxVal):
|
||||
def minmax(value: int, minVal: int, maxVal: int) -> int:
|
||||
"""Make sure an integer is between min and max value (inclusive).
|
||||
"""
|
||||
return min(maxVal, max(minVal, value))
|
||||
|
||||
|
||||
def checkIntTuple(value, valid, default):
|
||||
def checkIntTuple(value: int, valid: tuple | list | set, default: int) -> int:
|
||||
"""Check that an int is an element of a tuple. If it isn't, return
|
||||
the default value.
|
||||
"""
|
||||
@@ -213,7 +208,7 @@ def checkIntTuple(value, valid, default):
|
||||
# Formatting Functions
|
||||
# =============================================================================================== #
|
||||
|
||||
def formatInt(value):
|
||||
def formatInt(value: int) -> str:
|
||||
"""Formats an integer with k, M, G etc.
|
||||
"""
|
||||
if not isinstance(value, int):
|
||||
@@ -260,20 +255,19 @@ def formatTime(t: int) -> str:
|
||||
# String Functions
|
||||
# =============================================================================================== #
|
||||
|
||||
def simplified(string):
|
||||
"""Take a string an strip leading and trailing whitespaces, and
|
||||
def simplified(text: str) -> str:
|
||||
"""Take a string and strip leading and trailing whitespaces, and
|
||||
replace all occurences of (multiple) whitespaces with a 0x20 space.
|
||||
"""
|
||||
return " ".join(str(string).strip().split())
|
||||
return " ".join(str(text).strip().split())
|
||||
|
||||
|
||||
def yesNo(value):
|
||||
"""Convert a boolean evaluated variable to a yes or no.
|
||||
"""
|
||||
def yesNo(value: int | bool | None) -> Literal["yes", "no"]:
|
||||
"""Convert a boolean evaluated variable to a yes or no."""
|
||||
return "yes" if value else "no"
|
||||
|
||||
|
||||
def transferCase(source, target):
|
||||
def transferCase(source: str, target: str) -> str:
|
||||
"""Transfers the case of the source word to the target word. This
|
||||
will consider all upper or lower, and first char capitalisation.
|
||||
"""
|
||||
@@ -295,7 +289,7 @@ def transferCase(source, target):
|
||||
return theResult
|
||||
|
||||
|
||||
def fuzzyTime(seconds):
|
||||
def fuzzyTime(seconds: int) -> str:
|
||||
"""Converts a time difference in seconds into a fuzzy time string.
|
||||
"""
|
||||
if seconds < 0:
|
||||
@@ -356,7 +350,7 @@ def fuzzyTime(seconds):
|
||||
).format(int(round(seconds/31557600)))
|
||||
|
||||
|
||||
def numberToRoman(value, toLower=False):
|
||||
def numberToRoman(value: int, toLower: bool = False) -> str:
|
||||
"""Convert an integer to a Roman number.
|
||||
"""
|
||||
if not isinstance(value, int):
|
||||
@@ -384,7 +378,7 @@ def numberToRoman(value, toLower=False):
|
||||
# Encoder Functions
|
||||
# =============================================================================================== #
|
||||
|
||||
def jsonEncode(data, n=0, nmax=0):
|
||||
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.
|
||||
"""
|
||||
@@ -474,7 +468,7 @@ def xmlIndent(tree: ET.Element | ET.ElementTree):
|
||||
# File and File System Functions
|
||||
# =============================================================================================== #
|
||||
|
||||
def readTextFile(path):
|
||||
def readTextFile(path: str | Path) -> str:
|
||||
"""Read the content of a text file in a robust manner.
|
||||
"""
|
||||
path = Path(path)
|
||||
@@ -488,7 +482,7 @@ def readTextFile(path):
|
||||
return ""
|
||||
|
||||
|
||||
def makeFileNameSafe(value):
|
||||
def makeFileNameSafe(value: str) -> str:
|
||||
"""Returns a filename safe string of the value.
|
||||
"""
|
||||
clean = ""
|
||||
@@ -498,7 +492,7 @@ def makeFileNameSafe(value):
|
||||
return clean
|
||||
|
||||
|
||||
def sha256sum(path):
|
||||
def sha256sum(path: str | Path) -> str | None:
|
||||
"""Make a shasum of a file using a buffer.
|
||||
Based on: https://stackoverflow.com/a/44873382/5825851
|
||||
"""
|
||||
@@ -521,7 +515,7 @@ def sha256sum(path):
|
||||
# Other Functions
|
||||
# =============================================================================================== #
|
||||
|
||||
def getGuiItem(objName):
|
||||
def getGuiItem(objName: str) -> QWidget | None:
|
||||
"""Returns a QtWidget based on its objectName.
|
||||
"""
|
||||
for qWidget in qApp.topLevelWidgets():
|
||||
@@ -535,50 +529,49 @@ def getGuiItem(objName):
|
||||
# =============================================================================================== #
|
||||
|
||||
class NWConfigParser(ConfigParser):
|
||||
"""Common: Adapted Config Parser
|
||||
|
||||
This is a subclass of the standard config parser that adds type safe
|
||||
helper functions, and support for lists.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def rdStr(self, section, option, default):
|
||||
"""Read string value.
|
||||
"""
|
||||
def rdStr(self, section: str, option: str, default: str) -> str:
|
||||
"""Read string value."""
|
||||
return self.get(section, option, fallback=default)
|
||||
|
||||
def rdInt(self, section, option, default):
|
||||
"""Read integer value.
|
||||
"""
|
||||
def rdInt(self, section: str, option: str, default: int) -> int:
|
||||
"""Read integer value."""
|
||||
try:
|
||||
return self.getint(section, option, fallback=default)
|
||||
except ValueError:
|
||||
logger.error("Could not read '%s':'%s' from config", section, option)
|
||||
return default
|
||||
|
||||
def rdFlt(self, section, option, default):
|
||||
"""Read float value.
|
||||
"""
|
||||
def rdFlt(self, section: str, option: str, default: float) -> float:
|
||||
"""Read float value."""
|
||||
try:
|
||||
return self.getfloat(section, option, fallback=default)
|
||||
except ValueError:
|
||||
logger.error("Could not read '%s':'%s' from config", section, option)
|
||||
return default
|
||||
|
||||
def rdBool(self, section, option, default):
|
||||
"""Read boolean value.
|
||||
"""
|
||||
def rdBool(self, section: str, option: str, default: bool) -> bool:
|
||||
"""Read boolean value."""
|
||||
try:
|
||||
return self.getboolean(section, option, fallback=default)
|
||||
except ValueError:
|
||||
logger.error("Could not read '%s':'%s' from config", section, option)
|
||||
return default
|
||||
|
||||
def rdPath(self, section, option, default):
|
||||
"""Read a path value.
|
||||
"""
|
||||
def rdPath(self, section: str, option: str, default: Path) -> Path:
|
||||
"""Read a Path value."""
|
||||
return checkPath(self.get(section, option, fallback=default), default)
|
||||
|
||||
def rdStrList(self, section, option, default):
|
||||
"""Read string list.
|
||||
"""
|
||||
def rdStrList(self, section: str, option: str, default: list[str]) -> list[str]:
|
||||
"""Read string list."""
|
||||
result = default.copy() if isinstance(default, list) else []
|
||||
if self.has_option(section, option):
|
||||
data = self.get(section, option, fallback="").split(",")
|
||||
@@ -586,9 +579,8 @@ class NWConfigParser(ConfigParser):
|
||||
result[i] = data[i].strip()
|
||||
return result
|
||||
|
||||
def rdIntList(self, section, option, default):
|
||||
"""Read integer list.
|
||||
"""
|
||||
def rdIntList(self, section: str, option: str, default: list[int]) -> list[int]:
|
||||
"""Read integer list."""
|
||||
result = default.copy() if isinstance(default, list) else []
|
||||
if self.has_option(section, option):
|
||||
data = self.get(section, option, fallback="").split(",")
|
||||
|
||||
@@ -150,17 +150,17 @@ class BuildSettings:
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the build name."""
|
||||
"""The build name."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def buildID(self) -> str:
|
||||
"""Return the build ID."""
|
||||
"""The build ID as an UUID."""
|
||||
return self._uuid
|
||||
|
||||
@property
|
||||
def changed(self) -> bool:
|
||||
"""Return the changed status of the build."""
|
||||
"""The changed status of the build."""
|
||||
return self._changed
|
||||
|
||||
##
|
||||
@@ -190,7 +190,7 @@ class BuildSettings:
|
||||
return 0
|
||||
|
||||
def getFloat(self, key: str) -> float:
|
||||
"""Type safe value access for float."""
|
||||
"""Type safe value access for floats."""
|
||||
value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None)[1]))
|
||||
if isinstance(value, float):
|
||||
return value
|
||||
|
||||
+27
-19
@@ -1,7 +1,6 @@
|
||||
"""
|
||||
novelWriter – Project Options Cache
|
||||
===================================
|
||||
Data class for user-defined GUI project options
|
||||
|
||||
File History:
|
||||
Created: 2019-10-21 [0.3.1]
|
||||
@@ -23,17 +22,22 @@ General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from pathlib import Path
|
||||
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.common import checkBool, checkFloat, checkInt, checkString
|
||||
from novelwriter.constants import nwFiles
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.core.project import NWProject
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
VALID_MAP = {
|
||||
@@ -63,9 +67,15 @@ VALID_MAP = {
|
||||
|
||||
|
||||
class OptionState:
|
||||
"""Core: GUI Options Storage
|
||||
|
||||
def __init__(self, theProject):
|
||||
self.theProject = theProject
|
||||
A class for storing the state of the GUI. The data is stored per
|
||||
project. Settings that should be project-independent are stored in
|
||||
the Config instead.
|
||||
"""
|
||||
|
||||
def __init__(self, project: NWProject):
|
||||
self._project = project
|
||||
self._theState = {}
|
||||
return
|
||||
|
||||
@@ -73,10 +83,10 @@ class OptionState:
|
||||
# Load and Save Cache
|
||||
##
|
||||
|
||||
def loadSettings(self):
|
||||
def loadSettings(self) -> bool:
|
||||
"""Load the options dictionary from the project settings file.
|
||||
"""
|
||||
stateFile = self.theProject.storage.getMetaFile(nwFiles.OPTS_FILE)
|
||||
stateFile = self._project.storage.getMetaFile(nwFiles.OPTS_FILE)
|
||||
if not isinstance(stateFile, Path):
|
||||
return False
|
||||
|
||||
@@ -101,10 +111,9 @@ class OptionState:
|
||||
|
||||
return True
|
||||
|
||||
def saveSettings(self):
|
||||
"""Save the options dictionary to the project settings file.
|
||||
"""
|
||||
stateFile = self.theProject.storage.getMetaFile(nwFiles.OPTS_FILE)
|
||||
def saveSettings(self) -> bool:
|
||||
"""Save the options dictionary to the project settings file."""
|
||||
stateFile = self._project.storage.getMetaFile(nwFiles.OPTS_FILE)
|
||||
if not isinstance(stateFile, Path):
|
||||
return False
|
||||
|
||||
@@ -123,9 +132,8 @@ class OptionState:
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setValue(self, group, name, value):
|
||||
"""Save a value, with a given group and name.
|
||||
"""
|
||||
def setValue(self, group: str, name: str, value: Any) -> bool:
|
||||
"""Save a value, with a given group and name."""
|
||||
if group not in VALID_MAP:
|
||||
logger.error("Unknown option group '%s'", group)
|
||||
return False
|
||||
@@ -148,7 +156,7 @@ class OptionState:
|
||||
# Getters
|
||||
##
|
||||
|
||||
def getValue(self, group, name, default):
|
||||
def getValue(self, group: str, name: str, default: Any) -> Any:
|
||||
"""Return an arbitrary type value, if it exists. Otherwise,
|
||||
return the default value.
|
||||
"""
|
||||
@@ -156,7 +164,7 @@ class OptionState:
|
||||
return self._theState[group].get(name, default)
|
||||
return default
|
||||
|
||||
def getString(self, group, name, default):
|
||||
def getString(self, group: str, name: str, default: str) -> str:
|
||||
"""Return the value as a string, if it exists. Otherwise, return
|
||||
the default value.
|
||||
"""
|
||||
@@ -164,7 +172,7 @@ class OptionState:
|
||||
return checkString(self._theState[group].get(name, default), default)
|
||||
return default
|
||||
|
||||
def getInt(self, group, name, default):
|
||||
def getInt(self, group: str, name: str, default: int) -> int:
|
||||
"""Return the value as an int, if it exists. Otherwise, return
|
||||
the default value.
|
||||
"""
|
||||
@@ -172,7 +180,7 @@ class OptionState:
|
||||
return checkInt(self._theState[group].get(name, default), default)
|
||||
return default
|
||||
|
||||
def getFloat(self, group, name, default):
|
||||
def getFloat(self, group: str, name: str, default: float) -> float:
|
||||
"""Return the value as a float, if it exists. Otherwise, return
|
||||
the default value.
|
||||
"""
|
||||
@@ -180,7 +188,7 @@ class OptionState:
|
||||
return checkFloat(self._theState[group].get(name, default), default)
|
||||
return default
|
||||
|
||||
def getBool(self, group, name, default):
|
||||
def getBool(self, group: str, name: str, default: bool) -> bool:
|
||||
"""Return the value as a bool, if it exists. Otherwise, return
|
||||
the default value.
|
||||
"""
|
||||
@@ -188,9 +196,9 @@ class OptionState:
|
||||
return checkBool(self._theState[group].get(name, default), default)
|
||||
return default
|
||||
|
||||
def getEnum(self, group, name, lookup, default):
|
||||
def getEnum(self, group: str, name: str, lookup: type, default: Enum) -> Enum:
|
||||
"""Return the value mapped to an enum. Otherwise return the
|
||||
default value
|
||||
default value.
|
||||
"""
|
||||
if issubclass(lookup, Enum):
|
||||
if group in self._theState:
|
||||
|
||||
@@ -285,15 +285,20 @@ class NWProjectData:
|
||||
self.theProject.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def setLastHandle(self, value: dict, component: str | None = None):
|
||||
"""Set a last used handle into the handle registry. If component
|
||||
is None, the value is assumed to be the whole dictionary of
|
||||
values.
|
||||
def setLastHandle(self, value: str | None, component: str):
|
||||
"""Set a last used handle into the handle registry for a given
|
||||
component.
|
||||
"""
|
||||
if isinstance(component, str):
|
||||
self._lastHandle[component] = checkStringNone(value, None)
|
||||
self.theProject.setProjectChanged(True)
|
||||
elif isinstance(value, dict):
|
||||
return
|
||||
|
||||
def setLastHandles(self, value: dict):
|
||||
"""Set the full last handles dictionary to a new set of values.
|
||||
This is intended to be used at project load.
|
||||
"""
|
||||
if isinstance(value, dict):
|
||||
for key, entry in value.items():
|
||||
if key in self._lastHandle:
|
||||
self._lastHandle[key] = str(entry) if isHandle(entry) else None
|
||||
|
||||
@@ -30,8 +30,8 @@ import xml.etree.ElementTree as ET
|
||||
|
||||
from enum import Enum
|
||||
from time import time
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
from pathlib import Path
|
||||
|
||||
from novelwriter import __version__, __hexversion__
|
||||
from novelwriter.common import (
|
||||
@@ -284,7 +284,7 @@ class ProjectXMLReader:
|
||||
elif xItem.tag == "importance":
|
||||
self._parseStatusImport(xItem, data.itemImport)
|
||||
elif xItem.tag == "lastHandle":
|
||||
data.setLastHandle(self._parseDictKeyText(xItem))
|
||||
data.setLastHandles(self._parseDictKeyText(xItem))
|
||||
elif xItem.tag == "autoReplace":
|
||||
if self._version >= 0x0102:
|
||||
data.setAutoReplace(self._parseDictKeyText(xItem))
|
||||
|
||||
@@ -170,7 +170,7 @@ class NWStorage:
|
||||
xmlWriter = ProjectXMLWriter(self._runtimePath)
|
||||
return xmlWriter
|
||||
|
||||
def getDocument(self, tHandle: str) -> NWDocument:
|
||||
def getDocument(self, tHandle: str | None) -> NWDocument:
|
||||
"""Return a document wrapper object."""
|
||||
if self._runtimePath is not None:
|
||||
return NWDocument(self._project, tHandle)
|
||||
|
||||
Reference in New Issue
Block a user