diff --git a/.gitignore b/.gitignore index e2ae38d70..60421e4c2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ IoTuring/Configurator/configurations.json* IoTuring/Configurator/dontmoveconf.itg* .venv build -*.egg-info \ No newline at end of file +*.egg-info diff --git a/IoTuring/Configurator/MenuPreset.py b/IoTuring/Configurator/MenuPreset.py index 3c671242c..5513c9f66 100644 --- a/IoTuring/Configurator/MenuPreset.py +++ b/IoTuring/Configurator/MenuPreset.py @@ -1,5 +1,5 @@ from __future__ import annotations - +from typing import TypedDict, List from InquirerPy import inquirer from IoTuring.Exceptions.Exceptions import UserCancelledException @@ -161,6 +161,10 @@ def HasQuestions(self) -> bool: """Check if this preset has any questions to ask""" return bool(self.presets) + class ChoiceDict(TypedDict): + name: str + value: str + def AddEntry(self, name, key, @@ -169,7 +173,7 @@ def AddEntry(self, display_if_key_value={}, instruction="", question_type="text", - choices=[]) -> None: + choices: list[ChoiceDict]=[]) -> None: """ Add an entry to the preset with: - key: the key to use in the dict @@ -187,6 +191,7 @@ def AddEntry(self, - instruction: more text to show - question_type: text, secret, integer, filepath, select or yesno - choices: only for select question type + * List of dicts with following keys and values [{name: "name", value: "value"}] """ if question_type not in ["text", "secret", "select", "yesno", "integer", "filepath"]: diff --git a/IoTuring/Entity/Deployments/ActiveWindow/ActiveWindow.py b/IoTuring/Entity/Deployments/ActiveWindow/ActiveWindow.py index 4f4366433..6d3faf5c8 100644 --- a/IoTuring/Entity/Deployments/ActiveWindow/ActiveWindow.py +++ b/IoTuring/Entity/Deployments/ActiveWindow/ActiveWindow.py @@ -41,7 +41,8 @@ def Update(self): if self.UpdateSpecificFunction: self.SetEntitySensorValue(KEY, str(self.UpdateSpecificFunction())) - def GetActiveWindow_macOS(self): + def GetActiveWindow_macOS(self) -> str: + """Get the active window title on macOS""" try: curr_app = NSWorkspace.sharedWorkspace().activeApplication() curr_app_name = curr_app['NSApplicationName'] @@ -49,10 +50,12 @@ def GetActiveWindow_macOS(self): except BaseException: return "Inactive" - def GetActiveWindow_Windows(self): + def GetActiveWindow_Windows(self) -> str: + """Get the active window title on Windows""" return GetWindowText(GetForegroundWindow()) def GetActiveWindow_Linux(self) -> str: + """Get the active window title on Linux""" p = self.RunCommand("xprop -root _NET_ACTIVE_WINDOW") if p.stdout: diff --git a/IoTuring/Entity/Deployments/AppInfo/AppInfo.py b/IoTuring/Entity/Deployments/AppInfo/AppInfo.py index bd58feee6..5b87abff6 100644 --- a/IoTuring/Entity/Deployments/AppInfo/AppInfo.py +++ b/IoTuring/Entity/Deployments/AppInfo/AppInfo.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import requests from IoTuring.Entity.Entity import Entity from IoTuring.Entity.EntityData import EntitySensor @@ -46,7 +48,7 @@ def Update(self): self.SetEntitySensorExtraAttribute(KEY_UPDATE, EXTRA_ATTRIBUTE_UPDATE_ERROR, GET_UPDATE_ERROR_MESSAGE) - def GetUpdateInformation(self): + def GetUpdateInformation(self) -> str | bool: """ Get the update information of IoTuring Returns False if no update is available @@ -70,7 +72,7 @@ def GetUpdateInformation(self): else: raise UpdateCheckException() -def versionToInt(version: str): +def versionToInt(version: str) -> int: return int(''.join([i for i in version if i.isdigit()])) class UpdateCheckException(Exception): diff --git a/IoTuring/Entity/Deployments/Battery/Battery.py b/IoTuring/Entity/Deployments/Battery/Battery.py index 9972bbff1..afbd3662b 100644 --- a/IoTuring/Entity/Deployments/Battery/Battery.py +++ b/IoTuring/Entity/Deployments/Battery/Battery.py @@ -1,4 +1,5 @@ from __future__ import annotations +from typing import TypedDict, Dict import psutil from IoTuring.Entity.Entity import Entity from IoTuring.Entity.EntityData import EntitySensor @@ -7,6 +8,10 @@ KEY_PERCENTAGE = 'percentage' KEY_CHARGING_STATUS = 'charging' +class BatteryInformation(TypedDict): + level: int + charging: bool + class Battery(Entity): NAME = "Battery" supports_charge = False @@ -34,9 +39,9 @@ def Update(self): self.SetEntitySensorValue( KEY_CHARGING_STATUS, str(batteryInfo['charging'])) - def GetBatteryInformation(self) -> dict: + def GetBatteryInformation(self) -> BatteryInformation: battery = psutil.sensors_battery() if not battery: raise Exception("No battery sensor for this host") - return {'level': battery.percent, 'charging': battery.power_plugged} + return BatteryInformation({'level': battery.percent, 'charging': battery.power_plugged}) diff --git a/IoTuring/Entity/Deployments/FileSwitch/FileSwitch.py b/IoTuring/Entity/Deployments/FileSwitch/FileSwitch.py index a440e6d74..6682ccc56 100644 --- a/IoTuring/Entity/Deployments/FileSwitch/FileSwitch.py +++ b/IoTuring/Entity/Deployments/FileSwitch/FileSwitch.py @@ -1,4 +1,5 @@ from pathlib import Path +from paho.mqtt.client import MQTTMessage from IoTuring.Entity.Entity import Entity from IoTuring.Entity.EntityData import EntityCommand, EntitySensor @@ -25,7 +26,7 @@ def Initialize(self): self.RegisterEntityCommand(EntityCommand( self, KEY_CMD, self.Callback, KEY_STATE)) - def Callback(self, message): + def Callback(self, message: MQTTMessage): payloadString = message.payload.decode('utf-8') if payloadString == "True": diff --git a/IoTuring/Entity/Deployments/Hostname/Hostname.py b/IoTuring/Entity/Deployments/Hostname/Hostname.py index eebb4816d..c975dc9b1 100644 --- a/IoTuring/Entity/Deployments/Hostname/Hostname.py +++ b/IoTuring/Entity/Deployments/Hostname/Hostname.py @@ -13,5 +13,5 @@ def Initialize(self): # The value for this sensor is static for the entire script run time self.SetEntitySensorValue(KEY_HOSTNAME, self.GetHostname()) - def GetHostname(self): + def GetHostname(self) -> str: return socket.gethostname() diff --git a/IoTuring/Entity/Deployments/Monitor/Monitor.py b/IoTuring/Entity/Deployments/Monitor/Monitor.py index a0c629e5e..dddad31fa 100644 --- a/IoTuring/Entity/Deployments/Monitor/Monitor.py +++ b/IoTuring/Entity/Deployments/Monitor/Monitor.py @@ -1,5 +1,6 @@ import ctypes import re +from paho.mqtt.client import MQTTMessage from IoTuring.Entity.Entity import Entity from IoTuring.Entity.EntityData import EntityCommand, EntitySensor @@ -26,7 +27,7 @@ def Initialize(self): self.RegisterEntityCommand(EntityCommand( self, KEY_CMD, self.Callback)) - def Callback(self, message): + def Callback(self, message: MQTTMessage): payloadString = message.payload.decode('utf-8') if payloadString == STATE_ON: diff --git a/IoTuring/Entity/Deployments/Notify/Notify.py b/IoTuring/Entity/Deployments/Notify/Notify.py index 7b3105088..cf993211d 100644 --- a/IoTuring/Entity/Deployments/Notify/Notify.py +++ b/IoTuring/Entity/Deployments/Notify/Notify.py @@ -6,6 +6,7 @@ import os import json +from paho.mqtt.client import MQTTMessage supports_win = True try: @@ -82,7 +83,7 @@ def Initialize(self): self.RegisterEntityCommand(EntityCommand(self, KEY, self.Callback)) - def Callback(self, message): + def Callback(self, message: MQTTMessage): if self.data_mode == MODE_DATA_VIA_PAYLOAD: # Get data from payload: payloadString = message.payload.decode('utf-8') diff --git a/IoTuring/Entity/Deployments/Power/Power.py b/IoTuring/Entity/Deployments/Power/Power.py index 0ad9cf8d5..d477d2cfa 100644 --- a/IoTuring/Entity/Deployments/Power/Power.py +++ b/IoTuring/Entity/Deployments/Power/Power.py @@ -1,3 +1,5 @@ +from paho.mqtt.client import MQTTMessage + from IoTuring.Entity.Entity import Entity from IoTuring.Entity.EntityData import EntityCommand from IoTuring.Exceptions.Exceptions import UnknownConfigKeyException @@ -64,7 +66,7 @@ def Initialize(self): self.RegisterEntityCommand(EntityCommand( self, command_key, self.Callback)) - def Callback(self, message): + def Callback(self, message: MQTTMessage): # From the topic we can find the command: key = message.topic.split("/")[-1] self.RunCommand( diff --git a/IoTuring/Entity/Deployments/Temperature/Temperature.py b/IoTuring/Entity/Deployments/Temperature/Temperature.py index 5965f1fef..87b31e323 100644 --- a/IoTuring/Entity/Deployments/Temperature/Temperature.py +++ b/IoTuring/Entity/Deployments/Temperature/Temperature.py @@ -1,4 +1,10 @@ +from __future__ import annotations + import psutil + +from typing import List +from psutil._common import shwtemp + from IoTuring.Entity.Entity import Entity from IoTuring.Entity.EntityData import EntitySensor from IoTuring.Entity.ValueFormat import ValueFormatter, ValueFormatterOptions @@ -120,7 +126,7 @@ def UpdateLinux(self): index += 1 - def packageNameToEntitySensorKey(self, packageName): + def packageNameToEntitySensorKey(self, packageName) -> str: return KEY_SENSOR_FORMAT.format(packageName) @@ -130,7 +136,7 @@ def CheckSystemSupport(cls): raise cls.UnsupportedOsException() class psutilTemperaturePackage(): - def __init__(self, packageName, packageData) -> None: + def __init__(self, packageName: str, packageData: List[shwtemp]) -> None: """ packageData is the value of the the dict returned by psutil.sensors_temperatures() """ self.packageName = packageName self.sensors = [] @@ -144,15 +150,15 @@ def getSensors(self) -> list: return self.sensors.copy() # Package stats strategies here: my choice is to return always the highest among the temperatures, critical: here will return the lowest - def getCurrent(self): + def getCurrent(self) -> float | None: """ Returns highest current temperature among this package sensors. None if any sensor has that data. """ highest = None for sensor in self.getSensors(): if sensor.hasCurrent() and (highest == None or highest < sensor.getCurrent()): - highest = sensor.getCurrent() + highest = sensor.getCurrent() return highest - def getHighest(self): + def getHighest(self) -> float | None: """ Returns highest highest temperature among this package sensors. None if any sensor has that data. """ highest = None for sensor in self.getSensors(): @@ -160,7 +166,7 @@ def getHighest(self): highest = sensor.getHighest() return highest - def getCritical(self): + def getCritical(self) -> float | None: """ Returns lower critical temperature among this package sensors. None if any sensor has that data. """ lowest = None for sensor in self.getSensors(): @@ -168,28 +174,28 @@ def getCritical(self): lowest = sensor.getCritical() return lowest - def hasCurrent(self): + def hasCurrent(self) -> bool: """ True if at least a sensor of the package has the current property """ for sensor in self.sensors: if sensor.hasCurrent(): return True return False - def hasHighest(self): + def hasHighest(self) -> bool: """ True if at least a sensor of the package has the highest property """ for sensor in self.sensors: if sensor.hasHighest(): return True return False - def hasCritical(self): + def hasCritical(self) -> bool: """ True if at least a sensor of the package has the critical property """ for sensor in self.sensors: if sensor.hasCritical(): return True return False - def getAttributesDict(self): + def getAttributesDict(self) -> dict: attributes = {} for index, sensor in enumerate(self.getSensors()): if sensor.hasLabel(): @@ -213,30 +219,30 @@ def __init__(self, sensorData) -> None: self.highest = sensorData[2] self.critical = sensorData[3] - def getCurrent(self): + def getCurrent(self) -> float | None: return self.current def getLabel(self) -> str: return self.label - def getHighest(self): + def getHighest(self) -> float | None: return self.highest - def getCritical(self): + def getCritical(self) -> float | None: return self.critical - def hasLabel(self): + def hasLabel(self) -> bool: """ True if a label is set for this sensor """ return not self.label == None and not self.label.strip() == "" - def hasCurrent(self): + def hasCurrent(self) -> bool: """ True if a current is set for this sensor """ return not self.current == None - def hasHighest(self): + def hasHighest(self) -> bool: """ True if a highest is set for this sensor """ return not self.highest == None - def hasCritical(self): + def hasCritical(self) -> bool: """ True if a critical is set for this sensor """ return not self.critical == None diff --git a/IoTuring/Entity/Deployments/Terminal/Terminal.py b/IoTuring/Entity/Deployments/Terminal/Terminal.py index dd048e885..6a90fba9d 100644 --- a/IoTuring/Entity/Deployments/Terminal/Terminal.py +++ b/IoTuring/Entity/Deployments/Terminal/Terminal.py @@ -3,6 +3,7 @@ from IoTuring.Entity.EntityData import EntityCommand, EntitySensor from IoTuring.Logger.consts import STATE_OFF, STATE_ON from IoTuring.Entity.ValueFormat import ValueFormatterOptions +from paho.mqtt.client import MQTTMessage import re KEY = "terminal" @@ -186,7 +187,7 @@ def Initialize(self): self.state = "" self.state_message = "" - def Callback(self, message): + def Callback(self, message: MQTTMessage): # Get data from payload: payloadString = message.payload.decode('utf-8') diff --git a/IoTuring/Entity/Deployments/Time/Time.py b/IoTuring/Entity/Deployments/Time/Time.py index 5a1b9f3ed..db6af00fe 100644 --- a/IoTuring/Entity/Deployments/Time/Time.py +++ b/IoTuring/Entity/Deployments/Time/Time.py @@ -14,5 +14,5 @@ def Initialize(self): def Update(self): self.SetEntitySensorValue(KEY_NOW, self.GetCurrentTime()) - def GetCurrentTime(self): + def GetCurrentTime(self) -> str: return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") diff --git a/IoTuring/Entity/Deployments/Username/Username.py b/IoTuring/Entity/Deployments/Username/Username.py index 48ae49b7c..d82ef8e11 100644 --- a/IoTuring/Entity/Deployments/Username/Username.py +++ b/IoTuring/Entity/Deployments/Username/Username.py @@ -13,7 +13,7 @@ def Initialize(self): self.SetEntitySensorValue(KEY_USERNAME, self.GetUsername()) - def GetUsername(self): + def GetUsername(self) -> str: # Gives user's home directory userhome = os.path.expanduser('~') diff --git a/IoTuring/Entity/Deployments/Volume/Volume.py b/IoTuring/Entity/Deployments/Volume/Volume.py index c10f12562..85f763447 100644 --- a/IoTuring/Entity/Deployments/Volume/Volume.py +++ b/IoTuring/Entity/Deployments/Volume/Volume.py @@ -1,4 +1,6 @@ import re +from typing import List +from paho.mqtt.client import MQTTMessage from IoTuring.Entity.Entity import Entity from IoTuring.Entity.EntityData import EntityCommand, EntitySensor @@ -64,7 +66,7 @@ def Update(self): self.SetEntitySensorExtraAttribute( KEY_STATE, EXTRA_KEY_MUTED_OUTPUT, output_muted) - def Callback(self, message): + def Callback(self, message: MQTTMessage): payloadString = message.payload.decode('utf-8') # parse the payload and get the volume number which is between 0 and 100 @@ -85,7 +87,7 @@ def UpdateMac(self): # result like: output volume:44, input volume:89, alert volume:100, output muted:false command = self.RunCommand( command=['osascript', '-e', 'get volume settings']) - result = command.stdout.strip().split(',') + result: List[str] | None = command.stdout.strip().split(',') output_volume = result[0].split(':')[1] input_volume = result[1].split(':')[1] diff --git a/IoTuring/Warehouse/Deployments/HomeAssistantWarehouse/HomeAssistantWarehouse.py b/IoTuring/Warehouse/Deployments/HomeAssistantWarehouse/HomeAssistantWarehouse.py index dc011e6ed..125d75380 100644 --- a/IoTuring/Warehouse/Deployments/HomeAssistantWarehouse/HomeAssistantWarehouse.py +++ b/IoTuring/Warehouse/Deployments/HomeAssistantWarehouse/HomeAssistantWarehouse.py @@ -4,6 +4,7 @@ import yaml import re import time +from paho.mqtt.client import MQTTMessage from typing import Callable from IoTuring.Configurator.MenuPreset import MenuPreset @@ -301,7 +302,7 @@ def GetConnectedSensor(self) -> HomeAssistantSensor | None: def GenerateCommandCallback(self) -> Callable: """ Generate the callback function """ - def CommandCallback(message): + def CommandCallback(message: MQTTMessage): status = self.entityCommand.CallCallback(message) if status and self.wh.client.IsConnected(): if self.connected_sensor: diff --git a/tests/Entity/Deployments/Temperature/test_Temperature.py b/tests/Entity/Deployments/Temperature/test_Temperature.py index 16631799f..9ec137278 100644 --- a/tests/Entity/Deployments/Temperature/test_Temperature.py +++ b/tests/Entity/Deployments/Temperature/test_Temperature.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from IoTuring.Entity.Deployments.Temperature.Temperature import psutilTemperaturePackage, psutilTemperatureSensor class TestPsutilTemperatureSensor: