diff --git a/.gitignore b/.gitignore index 454a51c2d..45d828693 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ python_gammu.egg-info/ .coverage /.cache/ _codeql_detected_source_root +/uv.lock diff --git a/examples/__init__.py b/examples/__init__.py deleted file mode 100644 index 4c15d8c29..000000000 --- a/examples/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# vim: expandtab sw=4 ts=4 sts=4: -# -# Copyright © 2003 - 2018 Michal Čihař -# -# This file is part of python-gammu -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# diff --git a/examples/addcontacts.py b/examples/addcontacts.py index 7b65f235f..8956ddec0 100755 --- a/examples/addcontacts.py +++ b/examples/addcontacts.py @@ -26,7 +26,7 @@ import gammu -def main(): +def main() -> None: if len(sys.argv) != 3: print("This requires two parameters: memory_type and backup file (eg. vcard)!") sys.exit(1) diff --git a/examples/addfile.py b/examples/addfile.py index 5ff5fc0a3..bfc426151 100755 --- a/examples/addfile.py +++ b/examples/addfile.py @@ -22,18 +22,18 @@ import os +import pathlib import sys import gammu -def main(): +def main() -> None: if len(sys.argv) != 3: print("This requires two parameters: file to upload and path!") sys.exit(1) - with open(sys.argv[1], "rb") as handle: - data = handle.read() + data = pathlib.Path(sys.argv[1]).read_bytes() state_machine = gammu.StateMachine() state_machine.ReadConfig() diff --git a/examples/async.py b/examples/async.py old mode 100644 new mode 100755 index 5c4ff9a96..5ed10ed11 --- a/examples/async.py +++ b/examples/async.py @@ -19,10 +19,10 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ -python-gammu - Phone communication library -Gammu asynchronous wrapper example with asyncio. This allows your application to care -only about handling received data and not about phone communication -details. +Gammu asynchronous wrapper example with asyncio. + +This allows your application to care only about handling received data and not +about phone communication details. """ import asyncio @@ -32,7 +32,7 @@ import gammu.asyncworker -async def send_message_async(state_machine, number, message): +async def send_message_async(state_machine, number, message) -> None: smsinfo = { "Class": -1, "Unicode": False, @@ -41,15 +41,15 @@ async def send_message_async(state_machine, number, message): # Encode messages encoded = gammu.EncodeSMS(smsinfo) # Send messages - for message in encoded: + for encoded_message in encoded: # Fill in numbers - message["SMSC"] = {"Location": 1} - message["Number"] = number + encoded_message["SMSC"] = {"Location": 1} + encoded_message["Number"] = number # Actually send the message - await state_machine.send_sms_async(message) + await state_machine.send_sms_async(encoded_message) -async def get_network_info(worker): +async def get_network_info(worker) -> None: info = await worker.get_network_info_async() print("NetworkName:", info["NetworkName"]) @@ -67,7 +67,7 @@ async def get_network_info(worker): print(" LAC:", info["LAC"]) -async def get_info(worker): +async def get_info(worker) -> None: print("Phone information:") manufacturer = await worker.get_manufacturer_async() print("{:<15}: {}".format("Manufacturer", manufacturer)) @@ -79,11 +79,11 @@ async def get_info(worker): print("{:<15}: {}".format("Firmware", firmware[0])) -async def main(): +async def main() -> None: gammu.SetDebugFile(sys.stderr) gammu.SetDebugLevel("textall") - config = dict(Device="/dev/ttyS6", Connection="at") + config = {"Device": "/dev/ttyS6", "Connection": "at"} worker = gammu.asyncworker.GammuAsyncWorker() worker.configure(config) @@ -102,12 +102,12 @@ async def main(): try: signal = await worker.get_signal_quality_async() print("Signal is at {:d}%".format(signal["SignalPercent"])) - except Exception as e: + except Exception as e: # noqa: BLE001 print(f"Exception reading signal: {e}") await asyncio.sleep(10) - except Exception as e: + except Exception as e: # noqa: BLE001 print("Exception:") print(e) diff --git a/examples/debugging.py b/examples/debugging.py index 95a78ca64..68964c2aa 100755 --- a/examples/debugging.py +++ b/examples/debugging.py @@ -26,7 +26,7 @@ import gammu -def main(): +def main() -> None: # Global debug level gammu.SetDebugFile(sys.stderr) gammu.SetDebugLevel("textall") diff --git a/examples/doc-exceptions.py b/examples/doc-exceptions.py index ec6471819..2697186c0 100755 --- a/examples/doc-exceptions.py +++ b/examples/doc-exceptions.py @@ -24,13 +24,13 @@ import gammu.exception -def main(): +def main() -> None: for exc in sorted(gammu.exception.__all__): print(f".. exception:: gammu.{exc}") print() doc = getattr(gammu.exception, exc).__doc__ - for doc in doc.splitlines(): - print(f" {doc}") + for line in doc.splitlines(): + print(f" {line}") print() diff --git a/examples/dummy_phone.py b/examples/dummy_phone.py index 5cd3f675c..2dfae9978 100755 --- a/examples/dummy_phone.py +++ b/examples/dummy_phone.py @@ -20,8 +20,10 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # """ -python-gammu - Test script to test several Gammu operations -(usually using dummy driver, but it depends on config) +Dummy driver example. + +Test script to test several Gammu operations (usually using dummy driver, but +it depends on config). """ import sys @@ -29,7 +31,7 @@ import gammu -def get_all_memory(state_machine, memory_type): +def get_all_memory(state_machine, memory_type) -> None: status = state_machine.GetMemoryStatus(Type=memory_type) remain = status["Used"] @@ -44,7 +46,7 @@ def get_all_memory(state_machine, memory_type): entry = state_machine.GetNextMemory( Location=entry["Location"], Type=memory_type ) - remain = remain - 1 + remain -= 1 print() print("{:<15}: {:d}".format("Location", entry["Location"])) @@ -55,7 +57,7 @@ def get_all_memory(state_machine, memory_type): print("{:<15}: {}".format(v["Type"], v["Value"])) -def get_all_calendar(state_machine): +def get_all_calendar(state_machine) -> None: status = state_machine.GetCalendarStatus() remain = status["Used"] @@ -68,7 +70,7 @@ def get_all_calendar(state_machine): start = False else: entry = state_machine.GetNextCalendar(Location=entry["Location"]) - remain = remain - 1 + remain -= 1 print() print("{:<20}: {:d}".format("Location", entry["Location"])) @@ -77,7 +79,7 @@ def get_all_calendar(state_machine): print("{:<20}: {}".format(v["Type"], v["Value"])) -def get_battery_status(state_machine): +def get_battery_status(state_machine) -> None: status = state_machine.GetBatteryCharge() for x in status: @@ -98,12 +100,12 @@ def get_all_sms(state_machine): start = False else: sms = state_machine.GetNextSMS(Location=sms[0]["Location"], Folder=0) - remain = remain - len(sms) + remain -= len(sms) return sms -def print_sms_header(message, folders): +def print_sms_header(message, folders) -> None: print() print("{:<15}: {}".format("Number", message["Number"])) print("{:<15}: {}".format("Date", message["DateTime"])) @@ -119,16 +121,16 @@ def print_sms_header(message, folders): print("{:<15}: {}".format("Validity", message["SMSC"]["Validity"])) -def print_all_sms(sms, folders): +def print_all_sms(sms, folders) -> None: for m in sms: print_sms_header(m, folders) print("\n{}".format(m["Text"])) -def link_all_sms(sms, folders): +def link_all_sms(sms, folders) -> None: data = gammu.LinkSMS([[msg] for msg in sms]) - for x in data: + for x in data: # noqa: PLR1702 v = gammu.DecodeSMS(x) m = x[0] @@ -155,7 +157,7 @@ def link_all_sms(sms, folders): print() -def get_all_todo(state_machine): +def get_all_todo(state_machine) -> None: status = state_machine.GetToDoStatus() remain = status["Used"] @@ -168,7 +170,7 @@ def get_all_todo(state_machine): start = False else: entry = state_machine.GetNextToDo(Location=entry["Location"]) - remain = remain - 1 + remain -= 1 print() print("{:<15}: {:d}".format("Location", entry["Location"])) @@ -191,7 +193,7 @@ def get_set_date_time(state_machine): return dt -def main(): +def main() -> None: if len(sys.argv) != 2: print("This requires one parameter with location of config file!") sys.exit(1) diff --git a/examples/filesystem_test.py b/examples/filesystem_test.py index 758d81356..eb1c54b09 100755 --- a/examples/filesystem_test.py +++ b/examples/filesystem_test.py @@ -20,9 +20,11 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # """ +Filesystem testing. + This file should provide me with a test frame for the filesystem functions. It can't be run automatically, but you should be able -to decide, whether the output looks sensible +to decide, whether the output looks sensible. BEWARE - the test WILL TOUCH AND WRITE FILESYSTEM!! @@ -51,7 +53,7 @@ import gammu -def main(): +def main() -> None: # noqa: PLR0912, PLR0915, C901 parser = argparse.ArgumentParser(usage="usage: %(prog)s [options]") parser.add_argument( @@ -182,13 +184,13 @@ def main(): else: attribute = "" if file_f["Protected"]: - attribute = attribute + "P" + attribute += "P" if file_f["ReadOnly"]: - attribute = attribute + "R" + attribute += "R" if file_f["Hidden"]: - attribute = attribute + "H" + attribute += "H" if file_f["System"]: - attribute = attribute + "S" + attribute += "S" print( "ID: " + file_f["ID_FullName"] @@ -234,13 +236,13 @@ def main(): else: attribute = "" if file_f["Protected"]: - attribute = attribute + "P" + attribute += "P" if file_f["ReadOnly"]: - attribute = attribute + "R" + attribute += "R" if file_f["Hidden"]: - attribute = attribute + "H" + attribute += "H" if file_f["System"]: - attribute = attribute + "S" + attribute += "S" print( "ID: " + file_f["ID_FullName"] @@ -290,13 +292,13 @@ def main(): else: attribute = "" if file_f["Protected"]: - attribute = attribute + "P" + attribute += "P" if file_f["ReadOnly"]: - attribute = attribute + "R" + attribute += "R" if file_f["Hidden"]: - attribute = attribute + "H" + attribute += "H" if file_f["System"]: - attribute = attribute + "S" + attribute += "S" print( "ID: " + file_f["ID_FullName"] diff --git a/examples/getallcalendar.py b/examples/getallcalendar.py index 79cda3f5b..827f2e147 100755 --- a/examples/getallcalendar.py +++ b/examples/getallcalendar.py @@ -19,7 +19,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -"""Example for reading calendar from phone""" +"""Example for reading calendar from phone.""" import gammu @@ -46,7 +46,7 @@ start = False else: entry = state_machine.GetNextCalendar(Location=entry["Location"]) - remain = remain - 1 + remain -= 1 # Display it print() diff --git a/examples/getallmemory.py b/examples/getallmemory.py index 33311f954..7a63a63b0 100755 --- a/examples/getallmemory.py +++ b/examples/getallmemory.py @@ -26,7 +26,7 @@ import gammu -def main(): +def main() -> None: state_machine = gammu.StateMachine() state_machine.ReadConfig() state_machine.Init() @@ -51,7 +51,7 @@ def main(): entry = state_machine.GetNextMemory( Location=entry["Location"], Type=memory_type ) - remain = remain - 1 + remain -= 1 print() print("{:<15}: {:d}".format("Location", entry["Location"])) diff --git a/examples/getallmemory_nonext.py b/examples/getallmemory_nonext.py index 88b0daa30..037a8f0e5 100755 --- a/examples/getallmemory_nonext.py +++ b/examples/getallmemory_nonext.py @@ -26,7 +26,7 @@ import gammu -def main(): +def main() -> None: state_machine = gammu.StateMachine() state_machine.ReadConfig() state_machine.Init() @@ -50,10 +50,10 @@ def main(): print("{:<15}: {:d}".format("Location", entry["Location"])) for v in entry["Entries"]: print("{:<15}: {}".format(v["Type"], str(v["Value"]))) - remain = remain - 1 + remain -= 1 except gammu.ERR_EMPTY: pass - location = location + 1 + location += 1 if __name__ == "__main__": diff --git a/examples/getallsms.py b/examples/getallsms.py index ebae30893..04a1b3fa5 100755 --- a/examples/getallsms.py +++ b/examples/getallsms.py @@ -24,7 +24,7 @@ import gammu -def main(): +def main() -> None: state_machine = gammu.StateMachine() state_machine.ReadConfig() state_machine.Init() @@ -42,7 +42,7 @@ def main(): start = False else: sms = state_machine.GetNextSMS(Location=sms[0]["Location"], Folder=0) - remain = remain - len(sms) + remain -= len(sms) for m in sms: print() diff --git a/examples/getallsms_decode.py b/examples/getallsms_decode.py index 8b91e363b..3ced0e029 100755 --- a/examples/getallsms_decode.py +++ b/examples/getallsms_decode.py @@ -41,7 +41,7 @@ start = False else: cursms = state_machine.GetNextSMS(Location=cursms[0]["Location"], Folder=0) - remain = remain - len(cursms) + remain -= len(cursms) sms.append(cursms) except gammu.ERR_EMPTY: # This error is raised when we've reached last entry diff --git a/examples/getalltodo.py b/examples/getalltodo.py index 3d0a09318..504fa0137 100755 --- a/examples/getalltodo.py +++ b/examples/getalltodo.py @@ -24,7 +24,7 @@ import gammu -def main(): +def main() -> None: state_machine = gammu.StateMachine() state_machine.ReadConfig() state_machine.Init() @@ -41,7 +41,7 @@ def main(): start = False else: entry = state_machine.GetNextToDo(Location=entry["Location"]) - remain = remain - 1 + remain -= 1 print() print("{:<15}: {:d}".format("Location", entry["Location"])) diff --git a/examples/incoming.py b/examples/incoming.py index 0a5122356..8f8114dbe 100755 --- a/examples/incoming.py +++ b/examples/incoming.py @@ -26,9 +26,9 @@ import gammu -def callback(state_machine, callback_type, data): +def callback(state_machine, callback_type, data) -> None: """ - This callback receives notification about incoming event. + Callback for notification about incoming event. @param state_machine: state machine which invoked action @type state_machine: gammu.StateMachine @@ -41,7 +41,7 @@ def callback(state_machine, callback_type, data): print(data) -def try_enable(call, name): +def try_enable(call, name) -> None: try: call() except gammu.ERR_NOTSUPPORTED: @@ -50,7 +50,7 @@ def try_enable(call, name): print(f"{name} notification is not enabled in Gammu.") -def main(): +def main() -> None: # Create state machine state_machine = gammu.StateMachine() # Read gammurc diff --git a/examples/listfilesystem.py b/examples/listfilesystem.py index 3825ef3cc..d2f30072e 100755 --- a/examples/listfilesystem.py +++ b/examples/listfilesystem.py @@ -20,7 +20,7 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # """ -Example for usage of GetNextFileFolder, which is oriented at +Example for usage of GetNextFileFolder, which is oriented at. gammu --getfilesystem @@ -69,10 +69,7 @@ # Get wished listing from commandline (if provided - else assume level) # On commandline level or flat can be provided as parameters -if args.flat: - mode = "flat" -else: - mode = "level" +mode = "flat" if args.flat else "level" # Set locale to default locale (here relevant for printing of date) locale.setlocale(locale.LC_ALL, "") @@ -118,7 +115,7 @@ def FileToAttributeString(file_obj, filled=1): return protected + readonly + hidden + system -def Main(): +def Main() -> None: # Make sure we reset the pointer of the current entry to the first file_obj = NextFile(1) diff --git a/examples/mass_sms.py b/examples/mass_sms.py index c7e149bbc..f98dd21ec 100755 --- a/examples/mass_sms.py +++ b/examples/mass_sms.py @@ -19,14 +19,14 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -"""Sample script to show how to same SMS to multiple recipients""" +"""Sample script to show how to same SMS to multiple recipients.""" import sys import gammu # Check parameters count -if len(sys.argv) < 3 or sys.argv[1] in ["--help", "-h", "-?"]: +if len(sys.argv) < 3 or sys.argv[1] in {"--help", "-h", "-?"}: print("Usage: mass-sms [number]...") sys.exit(1) diff --git a/examples/network_info.py b/examples/network_info.py index ca1743aab..4c0f0039d 100755 --- a/examples/network_info.py +++ b/examples/network_info.py @@ -69,7 +69,7 @@ def get_network_info_with_name(state_machine): return netinfo -def main(): +def main() -> None: """Main function to demonstrate network info lookup.""" # Create state machine state_machine = gammu.StateMachine() diff --git a/examples/read_sms_backup.py b/examples/read_sms_backup.py index 8fad6e32f..644fbcef0 100755 --- a/examples/read_sms_backup.py +++ b/examples/read_sms_backup.py @@ -27,7 +27,7 @@ import gammu -def main(): +def main() -> None: if len(sys.argv) != 2: print("This requires parameter: backup file!") sys.exit(1) @@ -43,7 +43,7 @@ def main(): data = gammu.LinkSMS(messages) - for message in data: + for message in data: # noqa: PLR1702 decoded = gammu.DecodeSMS(message) part = message[0] diff --git a/examples/savesmspercontact.py b/examples/savesmspercontact.py index d3b3014fa..9680db5a8 100755 --- a/examples/savesmspercontact.py +++ b/examples/savesmspercontact.py @@ -28,7 +28,7 @@ import gammu -def createFolderIfNotExist(path): +def createFolderIfNotExist(path) -> None: try: os.makedirs(path) except OSError as exception: @@ -42,8 +42,7 @@ def getInternationalizedNumber(number): if number.startswith("0"): return number.replace("0", "+49", 1) - else: - return number + return number def getFilename(mydir, mysms): @@ -55,14 +54,14 @@ def getFilename(mydir, mysms): nextitem = 0 for i in myfiles: - match = re.match("^Unknown-([0-9]*)", i) + match = re.match(r"^Unknown-([0-9]*)", i) if match and int(match.group(1)) > nextitem: nextitem = int(match.group(1)) return "Unknown-" + str(nextitem + 1) -def saveSMS(mysms, all_contacts): +def saveSMS(mysms, all_contacts) -> None: my_number = getInternationalizedNumber(mysms[0]["Number"]) try: @@ -74,32 +73,31 @@ def saveSMS(mysms, all_contacts): myfile = getFilename(mydir, mysms) - with open(os.path.join(mydir, myfile), "a") as handle: - for i in mysms: - handle.write(i["Text"].encode("UTF-8")) - handle.write(b"\n") + with open(os.path.join(mydir, myfile), "a", encoding="utf-8") as handle: + handle.writelines(i["Text"] for i in mysms) + handle.write("\n") def getContacts(state_machine): # Get all contacts remaining = state_machine.GetMemoryStatus(Type="SM")["Used"] - contacts = dict() + contacts = {} start = True try: while remaining > 0: if start: - entry = state_machine.GetNextMemory(Start=True, Type="SM") + memory_entry = state_machine.GetNextMemory(Start=True, Type="SM") start = False else: - entry = state_machine.GetNextMemory( - Location=entry["Location"], Type="SM" + memory_entry = state_machine.GetNextMemory( + Location=memory_entry["Location"], Type="SM" ) - remaining = remaining - 1 + remaining -= 1 - numbers = list() - for entry in entry["Entries"]: + numbers = [] + for entry in memory_entry["Entries"]: if entry["Type"] == "Text_FirstName": name = entry["Value"] else: @@ -124,7 +122,7 @@ def getAndDeleteAllSMS(state_machine): # Get all sms start = True - entries = list() + entries = [] try: while remaining > 0: @@ -136,7 +134,7 @@ def getAndDeleteAllSMS(state_machine): Folder=0, Location=entry[0]["Location"] ) - remaining = remaining - 1 + remaining -= 1 entries.append(entry) # delete retrieved sms @@ -148,12 +146,10 @@ def getAndDeleteAllSMS(state_machine): print("Failed to read messages!") # Link all SMS when there are concatenated messages - entries = gammu.LinkSMS(entries) + return gammu.LinkSMS(entries) - return entries - -def main(): +def main() -> None: # Get all contacts state_machine = gammu.StateMachine() state_machine.ReadConfig() diff --git a/examples/sendlongsms.py b/examples/sendlongsms.py index a0b21a7b0..5c6413f7a 100755 --- a/examples/sendlongsms.py +++ b/examples/sendlongsms.py @@ -19,7 +19,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -"""Sample script to show how to send long (multipart) SMS""" +"""Sample script to show how to send long (multipart) SMS.""" import sys diff --git a/examples/sendsms.py b/examples/sendsms.py index f127b27f0..b3bc6773c 100755 --- a/examples/sendsms.py +++ b/examples/sendsms.py @@ -19,9 +19,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -""" -Sample script to show how to send SMS -""" +"""Sample script to show how to send SMS.""" import sys diff --git a/examples/service_numbers.py b/examples/service_numbers.py index cced8605e..7cbb7f7b2 100755 --- a/examples/service_numbers.py +++ b/examples/service_numbers.py @@ -19,84 +19,75 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -""" -Service numbers dialogue example. -""" +"""Service numbers dialogue example.""" import sys import gammu -REPLY = False +class ServiceHandler: + received_reply = False -def callback(state_machine, callback_type, data): - """ - Callback on USSD data. - """ - global REPLY - if callback_type != "USSD": - print(f"Unexpected event type: {callback_type}") - sys.exit(1) + def __init__(self) -> None: + self.state_machine = self.get_state_machine() - REPLY = True + def callback(self, _state_machine, callback_type, data) -> None: + """Callback on USSD data.""" + if callback_type != "USSD": + print(f"Unexpected event type: {callback_type}") + sys.exit(1) - print("Network reply:") - print("Status: {}".format(data["Status"])) - print(data["Text"]) + self.received_reply = True - if data["Status"] == "ActionNeeded": - do_service(state_machine) + print("Network reply:") + print("Status: {}".format(data["Status"])) + print(data["Text"]) + if data["Status"] == "ActionNeeded": + self.do_service() -def init(): - """ - Initializes gammu and callbacks. - """ - state_machine = gammu.StateMachine() - if len(sys.argv) >= 2: - state_machine.ReadConfig(Filename=sys.argv[1]) - else: - state_machine.ReadConfig() - state_machine.Init() - state_machine.SetIncomingCallback(callback) - try: - state_machine.SetIncomingUSSD() - except gammu.ERR_NOTSUPPORTED: - print("Incoming USSD notification is not supported.") - sys.exit(1) - return state_machine - - -def do_service(state_machine): - """ - Main code to talk with worker. - """ - global REPLY - - if len(sys.argv) >= 3: - code = sys.argv[2] - del sys.argv[2] - else: - prompt = "Enter code (empty string to end): " + def get_state_machine(self): + """Initializes gammu and callbacks.""" + state_machine = gammu.StateMachine() + if len(sys.argv) >= 2: + state_machine.ReadConfig(Filename=sys.argv[1]) + else: + state_machine.ReadConfig() + state_machine.Init() + state_machine.SetIncomingCallback(self.callback) try: - code = raw_input(prompt) - except NameError: - code = input(prompt) - if code != "": - print("Talking to network...") - REPLY = False - state_machine.DialService(code) - loops = 0 - while not REPLY and loops < 10: - state_machine.ReadDevice() - loops += 1 - - -def main(): - state_machine = init() + state_machine.SetIncomingUSSD() + except gammu.ERR_NOTSUPPORTED: + print("Incoming USSD notification is not supported.") + sys.exit(1) + return state_machine + + def do_service(self) -> None: + """Main code to talk with worker.""" + if len(sys.argv) >= 3: + code = sys.argv[2] + del sys.argv[2] + else: + prompt = "Enter code (empty string to end): " + try: + code = raw_input(prompt) + except NameError: + code = input(prompt) + if code: + print("Talking to network...") + self.received_reply = False + self.state_machine.DialService(code) + loops = 0 + while not self.received_reply and loops < 10: + self.state_machine.ReadDevice() + loops += 1 + + +def main() -> None: print("This example shows interaction with network using service codes") - do_service(state_machine) + service = ServiceHandler() + service.do_service() if __name__ == "__main__": diff --git a/examples/sms_replier.py b/examples/sms_replier.py index a4325ade1..42d98c213 100755 --- a/examples/sms_replier.py +++ b/examples/sms_replier.py @@ -30,7 +30,7 @@ VERBOSE = False -def verbose_print(text): +def verbose_print(text) -> None: if VERBOSE: print(text) @@ -54,7 +54,7 @@ def reply_test(message): ] -def Callback(state_machine, callback_type, data): +def Callback(state_machine, callback_type, data) -> None: verbose_print(f"Received incoming event type {callback_type}, data:") if callback_type != "SMS": print("Unsupported event!") @@ -82,7 +82,7 @@ def Callback(state_machine, callback_type, data): break -def main(): +def main() -> None: state_machine = gammu.StateMachine() state_machine.ReadConfig() state_machine.Init() diff --git a/examples/smsd_inject.py b/examples/smsd_inject.py index fba216051..cdfa8db9d 100755 --- a/examples/smsd_inject.py +++ b/examples/smsd_inject.py @@ -19,7 +19,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -"""Sample script to show how to send SMS through SMSD""" +"""Sample script to show how to send SMS through SMSD.""" import sys diff --git a/examples/smsd_state.py b/examples/smsd_state.py index e7c02030f..5939b74da 100755 --- a/examples/smsd_state.py +++ b/examples/smsd_state.py @@ -19,7 +19,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -"""Sample script to show how to get SMSD status""" +"""Sample script to show how to get SMSD status.""" import gammu.smsd diff --git a/examples/vcs.py b/examples/vcs.py index 3ad01ec17..ca30994a2 100755 --- a/examples/vcs.py +++ b/examples/vcs.py @@ -19,17 +19,14 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -""" -Example for reading data from phone and converting it to and from -vCard, vTodo, vCalendar -""" +"""Example for reading data from phone and converting it to and from vCard, vTodo, vCalendar.""" import sys import gammu -def main(): +def main() -> None: state_machine = gammu.StateMachine() if len(sys.argv) == 2: state_machine.ReadConfig(Filename=sys.argv[1]) diff --git a/examples/worker.py b/examples/worker.py index 945f8f76b..ad8e07c79 100755 --- a/examples/worker.py +++ b/examples/worker.py @@ -19,11 +19,10 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ -python-gammu - Phone communication library +Gammu asynchronous wrapper example. -Gammu asynchronous wrapper example. This allows your application to care -only about handling received data and not about phone communication -details. +This allows your application to care only about handling received data and not +about phone communication details. """ import sys @@ -32,24 +31,20 @@ import gammu.worker -def callback(name, result, error, percents): +def callback(name, result, error, percents) -> None: """ - Callback which is executed when something is done. Please remember - this is called from different thread so it does not have to be save - to work with GUI here. + Callback which is executed when something is done. + + Please remember this is called from different thread so it does not have to + be save to work with GUI here. + """ - print( - "-> {} completed {:d}% with error {} , return value:".format( - name, percents, error - ) - ) + print(f"-> {name} completed {percents:d}% with error {error} , return value:") print(result) def read_config(): - """ - Reads gammu configuration. - """ + """Reads gammu configuration.""" state_machine = gammu.StateMachine() # This is hack and should be as parameter of this function if len(sys.argv) == 2: @@ -59,10 +54,8 @@ def read_config(): return state_machine.GetConfig() -def main(): - """ - Main code to talk with worker. - """ +def main() -> None: + """Main code to talk with worker.""" worker = gammu.worker.GammuWorker(callback) worker.configure(read_config()) # We can directly invoke commands diff --git a/gammu/__init__.py b/gammu/__init__.py index aa6adcb36..551043bbb 100644 --- a/gammu/__init__.py +++ b/gammu/__init__.py @@ -18,9 +18,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -""" -Phone communication library - python wrapper for Gammu library. -""" +"""Phone communication library - python wrapper for Gammu library.""" from gammu._gammu import * # noqa: F403 diff --git a/gammu/asyncworker.py b/gammu/asyncworker.py index ac3830973..09a28ab5b 100644 --- a/gammu/asyncworker.py +++ b/gammu/asyncworker.py @@ -1,3 +1,21 @@ +# Copyright © 2003 - 2018 Michal Čihař +# +# This file is part of python-gammu +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# """Async extensions for gammu.""" import asyncio @@ -9,11 +27,11 @@ class GammuAsyncThread(gammu.worker.GammuThread): """Thread for phone communication.""" - def __init__(self, queue, config, callback, pull_func): + def __init__(self, queue, config, callback, pull_func) -> None: """Initialize thread.""" super().__init__(queue, config, callback, pull_func) - def _do_command(self, future, cmd, params, percentage=100): + def _do_command(self, future, cmd, params, percentage=100) -> None: """Execute single command on phone.""" func = getattr(self._sm, cmd) result = None @@ -28,20 +46,21 @@ def _do_command(self, future, cmd, params, percentage=100): errcode = info.args[0]["Code"] error = gammu.ErrorNumbers[errcode] self._callback(future, result, error, percentage) - except Exception as exception: # pylint: disable=broad-except + # pylint: disable-next=broad-except + except Exception as exception: # noqa: BLE001 self._callback(future, None, exception, percentage) else: self._callback(future, result, None, percentage) -def gammu_pull_device(sm): +def gammu_pull_device(sm) -> None: sm.ReadDevice() class GammuAsyncWorker(gammu.worker.GammuWorker): """Extend gammu worker class for async operations.""" - def worker_callback(self, name, result, error, percents): + def worker_callback(self, name, result, error, percents) -> None: """Execute command from the thread worker.""" future = None if name == "Init" and self._init_future is not None: @@ -62,8 +81,9 @@ def worker_callback(self, name, result, error, percents): exception = gammu.GSMError(error) self._loop.call_soon_threadsafe(future.set_exception, exception) - def __init__(self, pull_func=gammu_pull_device): - """Initialize the worker class. + def __init__(self, pull_func=gammu_pull_device) -> None: + """ + Initialize the worker class. @param callback: See L{GammuThread.__init__} for description. """ @@ -74,7 +94,7 @@ def __init__(self, pull_func=gammu_pull_device): self._thread = None self._pull_func = pull_func - async def init_async(self): + async def init_async(self) -> None: """Connect to phone.""" self._init_future = self._loop.create_future() @@ -120,36 +140,32 @@ async def get_signal_quality_async(self): """Get signal quality from phone.""" future = self._loop.create_future() self.enqueue(future, commands=[("GetSignalQuality", ())]) - result = await future - return result + return await future async def send_sms_async(self, message): """Send sms message via the phone.""" future = self._loop.create_future() self.enqueue(future, commands=[("SendSMS", [message])]) - result = await future - return result + return await future async def set_incoming_callback_async(self, callback): """Set the callback to call from phone.""" future = self._loop.create_future() self.enqueue(future, commands=[("SetIncomingCallback", [callback])]) - result = await future - return result + return await future async def set_incoming_sms_async(self): """Activate SMS notifications from phone.""" future = self._loop.create_future() self.enqueue(future, commands=[("SetIncomingSMS", ())]) - result = await future - return result + return await future - async def terminate_async(self): + async def terminate_async(self) -> None: """Terminate phone communication.""" self._terminate_future = self._loop.create_future() self.enqueue("Terminate") await self._terminate_future - while self._thread.is_alive(): - await asyncio.sleep(5) + await asyncio.to_thread(self._thread.join) + self._thread = None diff --git a/gammu/data.py b/gammu/data.py index 0a50c063c..f831aee4f 100644 --- a/gammu/data.py +++ b/gammu/data.py @@ -482,13 +482,13 @@ ] __all__ = [ - "Errors", - "ErrorNumbers", - "Connections", - "MemoryValueTypes", "CalendarTypes", "CalendarValueTypes", + "Connections", + "ErrorNumbers", + "Errors", + "InternationalPrefixes", + "MemoryValueTypes", "TodoPriorities", "TodoValueTypes", - "InternationalPrefixes", ] diff --git a/gammu/exception.py b/gammu/exception.py index fb18c5d0e..5e5914304 100644 --- a/gammu/exception.py +++ b/gammu/exception.py @@ -18,9 +18,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -""" -Gammu exceptions. -""" +"""Gammu exceptions.""" import gammu._gammu from gammu import GSMError @@ -35,7 +33,7 @@ continue _temp = __import__("gammu._gammu", globals(), locals(), [_name], 0) locals()[_name] = getattr(_temp, _name) - __all__.append(_name) + __all__.append(_name) # noqa: PYI056 # Cleanup del _name diff --git a/gammu/smsd.py b/gammu/smsd.py index c5fec8862..ef659b507 100644 --- a/gammu/smsd.py +++ b/gammu/smsd.py @@ -18,9 +18,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -""" -SMSD wrapper layer. -""" +"""SMSD wrapper layer.""" from gammu import SMSD diff --git a/gammu/worker.py b/gammu/worker.py index 57baf21be..1a7040db1 100644 --- a/gammu/worker.py +++ b/gammu/worker.py @@ -32,11 +32,9 @@ class InvalidCommand(Exception): - """ - Exception indicating invalid command. - """ + """Exception indicating invalid command.""" - def __init__(self, value): + def __init__(self, value) -> None: """ Initializes exception. @@ -46,14 +44,12 @@ def __init__(self, value): super().__init__() self.value = value - def __str__(self): - """ - Returns textual representation of exception. - """ + def __str__(self) -> str: + """Returns textual representation of exception.""" return f'Invalid command: "{self.value}"' -def check_worker_command(command): +def check_worker_command(command) -> None: """ Checks whether command is valid. @@ -67,53 +63,38 @@ def check_worker_command(command): class GammuCommand: - """ - Storage of single command for gammu. - """ + """Storage of single command for gammu.""" - def __init__(self, command, params=None, percentage=100): - """ - Creates single command instance. - """ + def __init__(self, command, params=None, percentage=100) -> None: + """Creates single command instance.""" check_worker_command(command) self._command = command self._params = params self._percentage = percentage def get_command(self): - """ - Returns command name. - """ + """Returns command name.""" return self._command def get_params(self): - """ - Returns command params. - """ + """Returns command params.""" return self._params def get_percentage(self): - """ - Returns percentage of current task. - """ + """Returns percentage of current task.""" return self._percentage - def __str__(self): - """ - Returns textual representation. - """ + def __str__(self) -> str: + """Returns textual representation.""" if self._params is not None: return f"{self._command} {self._params}" - else: - return f"{self._command} ()" + return f"{self._command} ()" class GammuTask: - """ - Storage of tasks for gammu. - """ + """Storage of tasks for gammu.""" - def __init__(self, name, commands): + def __init__(self, name, commands) -> None: """ Creates single command instance. @@ -139,30 +120,24 @@ def __init__(self, name, commands): self._list.append(GammuCommand(cmd, params, percents)) def get_next(self): - """ - Returns next command to be executed as L{GammuCommand}. - """ + """Returns next command to be executed as L{GammuCommand}.""" result = self._list[self._pointer] self._pointer += 1 return result def get_name(self): - """ - Returns task name. - """ + """Returns task name.""" return self._name -def gammu_pull_device(state_machine): +def gammu_pull_device(state_machine) -> None: state_machine.ReadDevice() class GammuThread(threading.Thread): - """ - Thread for phone communication. - """ + """Thread for phone communication.""" - def __init__(self, queue, config, callback, pull_func=gammu_pull_device): + def __init__(self, queue, config, callback, pull_func=gammu_pull_device) -> None: """ Initialises thread data. @@ -190,10 +165,8 @@ def __init__(self, queue, config, callback, pull_func=gammu_pull_device): self._sm.SetConfig(0, config) self._pull_func = pull_func - def _do_command(self, name, cmd, params, percentage=100): - """ - Executes single command on phone. - """ + def _do_command(self, name, cmd, params, percentage=100) -> None: + """Executes single command on phone.""" func = getattr(self._sm, cmd) error = "ERR_NONE" result = None @@ -210,10 +183,11 @@ def _do_command(self, name, cmd, params, percentage=100): self._callback(name, result, error, percentage) - def run(self): + def run(self) -> None: """ - Thread body, which handles phone communication. This should not - be used from outside. + Thread body, which handles phone communication. + + This should not be used from outside. """ start = True while not self._kill: @@ -245,31 +219,28 @@ def run(self): # Read the device to catch possible incoming events try: self._pull_func(self._sm) - except Exception as ex: + except Exception as ex: # noqa: BLE001 self._callback("ReadDevice", None, ex, 0) - def kill(self): - """ - Forces thread end without emptying queue. - """ + def kill(self) -> None: + """Forces thread end without emptying queue.""" self._kill = True - def join(self, timeout=None): - """ - Terminates thread and waits for it. - """ + def join(self, timeout=None) -> None: + """Terminates thread and waits for it.""" self._terminate = True super().join(timeout) class GammuWorker: """ - Wrapper class for asynchronous communication with Gammu. It spawns - own thread and then passes all commands to this thread. When task is - done, caller is notified via callback. + Wrapper class for asynchronous communication with Gammu. + + It spawns own thread and then passes all commands to this thread. When task + is done, caller is notified via callback. """ - def __init__(self, callback, pull_func=gammu_pull_device): + def __init__(self, callback, pull_func=gammu_pull_device) -> None: """ Initializes worker class. @@ -282,7 +253,7 @@ def __init__(self, callback, pull_func=gammu_pull_device): self._queue = queue.Queue() self._pull_func = pull_func - def enqueue_command(self, command, params): + def enqueue_command(self, command, params) -> None: """ Enqueues command. @@ -294,7 +265,7 @@ def enqueue_command(self, command, params): """ self._queue.put(GammuTask(command, [(command, params)])) - def enqueue_task(self, command, commands): + def enqueue_task(self, command, commands) -> None: """ Enqueues task. @@ -306,7 +277,7 @@ def enqueue_task(self, command, commands): """ self._queue.put(GammuTask(command, commands)) - def enqueue(self, command, params=None, commands=None): + def enqueue(self, command, params=None, commands=None) -> None: """ Enqueues command or task. @@ -324,7 +295,7 @@ def enqueue(self, command, params=None, commands=None): else: self.enqueue_command(command, params) - def configure(self, config): + def configure(self, config) -> None: """ Configures gammu instance according to config. @@ -335,24 +306,18 @@ def configure(self, config): self._config = config def abort(self): - """ - Aborts any remaining operations. - """ + """Aborts any remaining operations.""" raise NotImplementedError - def initiate(self): - """ - Connects to phone. - """ + def initiate(self) -> None: + """Connects to phone.""" self._thread = GammuThread( self._queue, self._config, self._callback, self._pull_func ) self._thread.start() - def terminate(self, timeout=None): - """ - Terminates phone connection. - """ + def terminate(self, timeout=None) -> None: + """Terminates phone connection.""" self.enqueue("Terminate") self._thread.join(timeout) self._thread = None diff --git a/pyproject.toml b/pyproject.toml index f50787858..2a8fd43f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,6 +101,71 @@ line_length = 88 multi_line_output = 3 use_parentheses = true +[tool.ruff.format] +docstring-code-format = true + +[tool.ruff.lint] +extend-safe-fixes = [ + "ANN", + "D", + "EM101", + "EM102", + "FA102", + "FLY", + "SIM", + "TCH", + "UP" +] +future-annotations = true +ignore = [ + "ANN001", + "ANN002", + "ANN003", + "ANN201", + "ANN202", + "ARG001", + "ARG002", + "COM", # CONFIG: No trailing commas + "D100", + "D101", + "D102", + "D103", + "D104", + "D107", + "D203", # CONFIG: incompatible with D211 + "D206", # CONFIG: formatter + "D212", # CONFIG: incompatible with D213 + "D401", + "DOC201", + "DOC501", + "DTZ001", + "DTZ006", + "E501", # WONTFIX: we accept long strings (rest is formatted by black) + "FBT003", + "FIX002", + "N802", + "N818", + "PERF203", # WONFIX: affects only old Python versions + "PLR2004", + "PLR6301", + "PTH", + "RUF012", + "S607", + "SIM115", + "SLF001", + "TD002", + "TD003", + "TRY003", + "TRY300" +] +preview = true +select = ["ALL"] + +[tool.ruff.lint.per-file-ignores] +"examples/*.py" = ["T201"] +"setup.py" = ["T201"] +"test/*.py" = ["S101", "T201"] + [tool.setuptools] include-package-data = false packages = [ diff --git a/setup.py b/setup.py index c145c9e6a..bf426d177 100755 --- a/setup.py +++ b/setup.py @@ -19,14 +19,12 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -""" -python-gammu - Phone communication library -""" +"""python-gammu - Phone communication library.""" import glob import os import platform -import subprocess +import subprocess # noqa: S404 import sys from packaging.version import parse @@ -38,14 +36,14 @@ class GammuConfig: - def __init__(self): + def __init__(self) -> None: self.on_windows = platform.system() == "Windows" self.has_pkgconfig = self.check_pkconfig() self.has_env = "GAMMU_PATH" in os.environ self.path = self.lookup_path() self.use_pkgconfig = self.has_pkgconfig and not self.has_env - def check_pkconfig(self): + def check_pkconfig(self) -> bool | None: try: subprocess.check_output(["pkg-config", "--help"]) return True @@ -74,11 +72,12 @@ def lookup_path(self): include = self.config_path(path) if os.path.exists(include): return path + return None - def check_version(self): + def check_version(self) -> None: if self.use_pkgconfig: try: - subprocess.check_output( + subprocess.check_output( # noqa: S603 [ "pkg-config", "--print-errors", @@ -101,7 +100,7 @@ def check_version(self): sys.exit(101) version = None - with open(self.config_path(self.path)) as handle: + with open(self.config_path(self.path), encoding="utf-8") as handle: for line in handle: if line.startswith("#define GAMMU_VERSION "): version = parse(line.split('"')[1]) @@ -118,9 +117,7 @@ def get_libs(self): return output.replace("-l", "").strip().split() libs = ["Gammu", "gsmsd"] if self.on_windows: - libs.append("Advapi32") - libs.append("shfolder") - libs.append("shell32") + libs.extend(("Advapi32", "shfolder", "shell32")) else: libs.append("m") return libs @@ -145,7 +142,7 @@ def get_ldflags(self): .decode("utf-8") .strip() ) - elif self.on_windows: + if self.on_windows: return "/LIBPATH:{}".format(os.path.join(self.path, "lib")) return "-L{}".format(os.path.join(self.path, "lib")) @@ -157,7 +154,7 @@ def get_module(): module = Extension( "gammu._gammu", define_macros=[ - ("PYTHON_GAMMU_MAJOR_VERSION", VERSION.split(".")[0]), + ("PYTHON_GAMMU_MAJOR_VERSION", VERSION.split(".", maxsplit=1)[0]), ("PYTHON_GAMMU_MINOR_VERSION", VERSION.split(".")[1]), ], libraries=config.get_libs(), diff --git a/test/test_asyncworker.py b/test/test_asyncworker.py index 32243ce02..636cb2831 100644 --- a/test/test_asyncworker.py +++ b/test/test_asyncworker.py @@ -20,6 +20,8 @@ # import asyncio +import pytest + import gammu.asyncworker from .test_dummy import DummyTest @@ -43,7 +45,8 @@ }, ), ("GetModel", ("unknown", "Dummy")), - # ('GetFirmware', ('1.41.0', '20150101', 1.41)), # Mock is returning different values between the local workstation on the CI build + # Mock is returning different values between the local workstation on the CI build + # TODO: ('GetFirmware', ('1.41.0', '20150101', 1.41)), ( "GetSignalQuality", {"BitErrorRate": 0, "SignalPercent": 42, "SignalStrength": 42}, @@ -67,14 +70,14 @@ def wrapper(*args, **kwargs): class AsyncWorkerDummyTest(DummyTest): results = [] - def callback(self, name, result, error, percents): + def callback(self, name, result, error, percents) -> None: self.results.append((name, result, error, percents)) - def my_pull_func(self, sm): + def my_pull_func(self, sm) -> None: self.results.append(("pull_func", sm.ReadDevice())) @async_test - async def test_worker_async(self): + async def test_worker_async(self) -> None: self.results = [] worker = gammu.asyncworker.GammuAsyncWorker(self.my_pull_func) worker.configure(self.get_statemachine().GetConfig()) @@ -83,7 +86,7 @@ async def test_worker_async(self): self.results.append(("GetManufacturer", await worker.get_manufacturer_async())) self.results.append(("GetNetworkInfo", await worker.get_network_info_async())) self.results.append(("GetModel", await worker.get_model_async())) - # self.results.append(('GetFirmware', await worker.get_firmware_async())) + # TODO: self.results.append(('GetFirmware', await worker.get_firmware_async())) self.results.append( ("GetSignalQuality", await worker.get_signal_quality_async()) ) @@ -93,9 +96,9 @@ async def test_worker_async(self): "Number": "555-555-1234", } self.results.append(("SendSMS", await worker.send_sms_async(message))) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): await worker.send_sms_async(42) - with self.assertRaises(Exception): + with pytest.raises(TypeError): await worker.send_sms_async(dict(42)) self.results.append( ( @@ -109,4 +112,4 @@ async def test_worker_async(self): self.results.append(("Terminate", await worker.terminate_async())) self.maxDiff = None - self.assertEqual(WORKER_EXPECT, self.results) + assert self.results == WORKER_EXPECT diff --git a/test/test_backup.py b/test/test_backup.py index 135fe253b..de2524c6e 100644 --- a/test/test_backup.py +++ b/test/test_backup.py @@ -36,7 +36,7 @@ class BackupTest(unittest.TestCase): - def perform_test(self, filename, extensions): + def perform_test(self, filename, extensions) -> None: out_files = [ tempfile.NamedTemporaryFile(suffix=extension, delete=False) for extension in extensions @@ -56,15 +56,13 @@ def perform_test(self, filename, extensions): backup_2 = gammu.ReadBackup(out.name) # Check content length - self.assertEqual( - len(backup["Calendar"]), - len(backup_2["Calendar"]), - f"Failed to compare calendar in {filename}", + assert len(backup["Calendar"]) == len(backup_2["Calendar"]), ( + f"Failed to compare calendar in {filename}" ) - self.assertEqual( - len(backup["PhonePhonebook"]) + len(backup["SIMPhonebook"]), - len(backup_2["PhonePhonebook"]) + len(backup_2["SIMPhonebook"]), - f"Failed to compare phonebook in {filename}", + assert len(backup["PhonePhonebook"]) + len( + backup["SIMPhonebook"] + ) == len(backup_2["PhonePhonebook"]) + len(backup_2["SIMPhonebook"]), ( + f"Failed to compare phonebook in {filename}" ) # Try converting to .backup @@ -74,15 +72,15 @@ def perform_test(self, filename, extensions): os.unlink(handle.name) os.unlink(out_backup.name) - def test_convert_contacts(self): + def test_convert_contacts(self) -> None: for filename in TEST_FILES_CONTACTS: self.perform_test(filename, TEST_CONTACTS) - def test_convert_calendar(self): + def test_convert_calendar(self) -> None: for filename in TEST_FILES_CALENDAR: self.perform_test(filename, TEST_CALENDAR) - def test_calendar(self): + def test_calendar(self) -> None: entry = gammu.ReadBackup(os.path.join(TEST_DIR, "rrule.ics"))["Calendar"][0] # Convert it to vCard @@ -93,9 +91,9 @@ def test_calendar(self): entry2 = gammu.DecodeVCS(vc_entry) entry3 = gammu.DecodeICS(ic_entry) - self.assertEqual(entry2["Type"], entry3["Type"]) + assert entry2["Type"] == entry3["Type"] - def test_todo(self): + def test_todo(self) -> None: entry = gammu.ReadBackup(os.path.join(TEST_DIR, "02.vcs"))["ToDo"][0] # Convert it to vCard @@ -106,9 +104,9 @@ def test_todo(self): entry2 = gammu.DecodeVCS(vt_entry) entry3 = gammu.DecodeICS(it_entry) - self.assertEqual(entry2["Type"], entry3["Type"]) + assert entry2["Type"] == entry3["Type"] - def test_contact(self): + def test_contact(self) -> None: entry = gammu.ReadBackup(os.path.join(TEST_DIR, "gammu.vcf"))["PhonePhonebook"][ 0 ] @@ -119,4 +117,4 @@ def test_contact(self): # Convert it back to entry entry2 = gammu.DecodeVCARD(vc_entry) - self.assertEqual(entry, entry2) + assert entry == entry2 diff --git a/test/test_config.py b/test/test_config.py index 3e8ae6357..fc8fb3b28 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -18,17 +18,19 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -import os +import pathlib import tempfile import unittest +import pytest + import gammu from .test_sms import PDU_DATA class ConfigTest(unittest.TestCase): - def test_config_bool(self): + def test_config_bool(self) -> None: state_machine = gammu.StateMachine() state_machine.SetConfig( 0, @@ -45,9 +47,9 @@ def test_config_bool(self): }, ) cfg = state_machine.GetConfig(0) - self.assertEqual(cfg["StartInfo"], 1) + assert cfg["StartInfo"] == 1 - def test_config_string(self): + def test_config_string(self) -> None: state_machine = gammu.StateMachine() state_machine.SetConfig( 0, @@ -64,9 +66,9 @@ def test_config_string(self): }, ) cfg = state_machine.GetConfig(0) - self.assertEqual(cfg["StartInfo"], 1) + assert cfg["StartInfo"] == 1 - def test_config_none(self): + def test_config_none(self) -> None: state_machine = gammu.StateMachine() state_machine.SetConfig( 0, @@ -83,40 +85,39 @@ def test_config_none(self): }, ) cfg = state_machine.GetConfig(0) - self.assertEqual(cfg["StartInfo"], 0) + assert cfg["StartInfo"] == 0 - def test_init_error(self): - self.assertRaises(TypeError, gammu.StateMachine, Bar=1) + def test_init_error(self) -> None: + with pytest.raises(TypeError): + gammu.StateMachine(Bar=1) class DebugTest(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: gammu.SetDebugLevel("textall") - def check_operation(self, filename, handle=None): - """ - Executes gammu operation which causes debug logs. - """ + def check_operation(self, filename, handle=None) -> None: + """Executes gammu operation which causes debug logs.""" gammu.DecodePDU(PDU_DATA) gammu.SetDebugFile(None) if handle: handle.close() if filename is not None: - with open(filename) as handle: - self.assertTrue("SMS type: Status report" in handle.read()) + content = pathlib.Path(filename).read_text(encoding="utf-8") + assert "SMS type: Status report" in content - def test_file(self): + def test_file(self) -> None: testfile = tempfile.NamedTemporaryFile(suffix=".debug", delete=False) testfile.close() try: - handle = open(testfile.name, "w") + handle = pathlib.Path(testfile.name).open("w", encoding="utf-8") gammu.SetDebugFile(handle) self.check_operation(testfile.name, handle) finally: gammu.SetDebugFile(None) - os.unlink(testfile.name) + pathlib.Path(testfile.name).unlink() - def test_filename(self): + def test_filename(self) -> None: testfile = tempfile.NamedTemporaryFile(suffix=".debug", delete=False) testfile.close() try: @@ -124,21 +125,21 @@ def test_filename(self): self.check_operation(testfile.name) finally: gammu.SetDebugFile(None) - os.unlink(testfile.name) + pathlib.Path(testfile.name).unlink() - def test_none(self): + def test_none(self) -> None: gammu.SetDebugFile(None) self.check_operation(None) - def test_nothing(self): + def test_nothing(self) -> None: gammu.SetDebugLevel("nothing") testfile = tempfile.NamedTemporaryFile(suffix=".debug", delete=False) testfile.close() try: gammu.SetDebugFile(testfile.name) self.check_operation(None) - with open(testfile.name) as handle: - self.assertEqual("", handle.read()) + content = pathlib.Path(testfile.name).read_text(encoding="utf-8") + assert not content finally: gammu.SetDebugFile(None) - os.unlink(testfile.name) + pathlib.Path(testfile.name).unlink() diff --git a/test/test_data.py b/test/test_data.py index 7d89e60ef..d43252844 100644 --- a/test/test_data.py +++ b/test/test_data.py @@ -25,30 +25,30 @@ class DataTest(unittest.TestCase): - def test_connections(self): - self.assertTrue("at" in gammu.data.Connections) + def test_connections(self) -> None: + assert "at" in gammu.data.Connections - def test_errors(self): - self.assertTrue("ERR_INSTALL_NOT_FOUND" in gammu.data.Errors) - self.assertEqual(gammu.data.ErrorNumbers[73], "ERR_NETWORK_ERROR") + def test_errors(self) -> None: + assert "ERR_INSTALL_NOT_FOUND" in gammu.data.Errors + assert gammu.data.ErrorNumbers[73] == "ERR_NETWORK_ERROR" - def test_gsm_networks(self): + def test_gsm_networks(self) -> None: """Test that GSMNetworks dictionary is available and contains expected data.""" # GSMNetworks should be a dictionary available from gammu module - self.assertIsInstance(gammu.GSMNetworks, dict) + assert isinstance(gammu.GSMNetworks, dict) # Should have at least some networks - self.assertGreater(len(gammu.GSMNetworks), 0) + assert len(gammu.GSMNetworks) > 0 # Test a known network code (Finland Elisa) # Network code "244 05" should map to "Elisa" if "244 05" in gammu.GSMNetworks: - self.assertEqual(gammu.GSMNetworks["244 05"], "Elisa") + assert gammu.GSMNetworks["244 05"] == "Elisa" - def test_gsm_countries(self): + def test_gsm_countries(self) -> None: """Test that GSMCountries dictionary is available and contains expected data.""" # GSMCountries should be a dictionary available from gammu module - self.assertIsInstance(gammu.GSMCountries, dict) + assert isinstance(gammu.GSMCountries, dict) # Should have at least some countries - self.assertGreater(len(gammu.GSMCountries), 0) + assert len(gammu.GSMCountries) > 0 # Test a known country code (Finland) if "244" in gammu.GSMCountries: - self.assertEqual(gammu.GSMCountries["244"], "Finland") + assert gammu.GSMCountries["244"] == "Finland" diff --git a/test/test_dummy.py b/test/test_dummy.py index 6393e6a8c..8bbfb3a65 100644 --- a/test/test_dummy.py +++ b/test/test_dummy.py @@ -21,11 +21,14 @@ import datetime import os.path +import pathlib import platform import shutil import tempfile import unittest +import pytest + import gammu DUMMY_DIR = os.path.join(os.path.dirname(__file__), "data", "gammu-dummy") @@ -59,15 +62,16 @@ class DummyTest(unittest.TestCase): _called = False _state_machine = None - def setUp(self): + def setUp(self) -> None: self.test_dir = tempfile.mkdtemp() self.dummy_dir = os.path.join(self.test_dir, "gammu-dummy") self.config_name = os.path.join(self.test_dir, ".gammurc") shutil.copytree(DUMMY_DIR, self.dummy_dir) - with open(self.config_name, "w") as handle: - handle.write(CONFIGURATION.format(path=self.test_dir)) + pathlib.Path(self.config_name).write_text( + CONFIGURATION.format(path=self.test_dir), encoding="utf-8" + ) - def tearDown(self): + def tearDown(self) -> None: if self._state_machine: self._state_machine.Terminate() shutil.rmtree(self.test_dir) @@ -79,83 +83,73 @@ def get_statemachine(self): self._state_machine = state_machine return state_machine - def fake_incoming_call(self): - """Fake incoming call""" + def fake_incoming_call(self) -> None: + """Fake incoming call.""" filename = os.path.join(self.dummy_dir, "incoming-call") - with open(filename, "w") as handle: - handle.write("\n") + pathlib.Path(filename).write_text("\n", encoding="utf-8") - def check_incoming_call(self): - """Checks whether incoming call faking is supported""" + def check_incoming_call(self) -> None: + """Checks whether incoming call faking is supported.""" current = tuple(int(x) for x in gammu.Version()[2].split(".")) if current < (1, 37, 91): - raise unittest.SkipTest(f"Not supported in version {gammu.Version()[2]}") + pytest.skip(f"Not supported in version {gammu.Version()[2]}") - def call_callback(self, state_machine, response, data): - """ - Callback on USSD data. - """ + def call_callback(self, state_machine, response, data) -> None: + """Callback on USSD data.""" self._called = True - self.assertEqual(response, "Call") - self.assertEqual(data["Number"], "+800123456") + assert response == "Call" + assert data["Number"] == "+800123456" -class BasicDummyTest(DummyTest): - def test_model(self): +class BasicDummyTest(DummyTest): # noqa: PLR0904 + def test_model(self) -> None: state_machine = self.get_statemachine() - self.assertEqual(state_machine.GetModel()[1], "Dummy") + assert state_machine.GetModel()[1] == "Dummy" - def test_diverts(self): + def test_diverts(self) -> None: state_machine = self.get_statemachine() diverts = state_machine.GetCallDivert() - self.assertEqual( - diverts, - [{"CallType": "All", "Timeout": 0, "Number": "", "DivertType": "AllTypes"}], - ) + assert diverts == [ + {"CallType": "All", "Timeout": 0, "Number": "", "DivertType": "AllTypes"} + ] state_machine.SetCallDivert("AllTypes", "All", "123456789") diverts = state_machine.GetCallDivert() - self.assertEqual( - diverts, - [ - { - "CallType": "All", - "Timeout": 0, - "Number": "123456789", - "DivertType": "AllTypes", - } - ], - ) + assert diverts == [ + { + "CallType": "All", + "Timeout": 0, + "Number": "123456789", + "DivertType": "AllTypes", + } + ] - def test_dial(self): + def test_dial(self) -> None: state_machine = self.get_statemachine() state_machine.DialVoice("123456") - def test_battery(self): + def test_battery(self) -> None: state_machine = self.get_statemachine() status = state_machine.GetBatteryCharge() - self.assertEqual( - status, - { - "BatteryVoltage": 4200, - "PhoneTemperature": 22, - "BatteryTemperature": 22, - "ChargeState": "BatteryConnected", - "ChargeVoltage": 4200, - "BatteryCapacity": 2000, - "BatteryPercent": 100, - "ChargeCurrent": 0, - "PhoneCurrent": 500, - }, - ) + assert status == { + "BatteryVoltage": 4200, + "PhoneTemperature": 22, + "BatteryTemperature": 22, + "ChargeState": "BatteryConnected", + "ChargeVoltage": 4200, + "BatteryCapacity": 2000, + "BatteryPercent": 100, + "ChargeCurrent": 0, + "PhoneCurrent": 500, + } - def test_memory(self): + def test_memory(self) -> None: state_machine = self.get_statemachine() status = state_machine.GetMemoryStatus(Type="ME") remain = status["Used"] - self.assertEqual(status["Used"], 3) + assert status["Used"] == 3 start = True @@ -167,9 +161,9 @@ def test_memory(self): entry = state_machine.GetNextMemory( Location=entry["Location"], Type="ME" ) - remain = remain - 1 + remain -= 1 - def test_getmemory(self): + def test_getmemory(self) -> None: state_machine = self.get_statemachine() location = state_machine.AddMemory( @@ -183,15 +177,15 @@ def test_getmemory(self): ) read = state_machine.GetMemory("SM", location) - self.assertEqual(len(read["Entries"]), 2) + assert len(read["Entries"]) == 2 - def test_calendar(self): + def test_calendar(self) -> None: state_machine = self.get_statemachine() status = state_machine.GetCalendarStatus() remain = status["Used"] - self.assertEqual(status["Used"], 2) + assert status["Used"] == 2 start = True @@ -201,15 +195,15 @@ def test_calendar(self): start = False else: entry = state_machine.GetNextCalendar(Location=entry["Location"]) - remain = remain - 1 + remain -= 1 - def test_sms(self): + def test_sms(self) -> None: state_machine = self.get_statemachine() status = state_machine.GetSMSStatus() remain = status["SIMUsed"] + status["PhoneUsed"] + status["TemplatesUsed"] - self.assertEqual(remain, 6) + assert remain == 6 start = True @@ -223,22 +217,22 @@ def test_sms(self): sms.append( state_machine.GetNextSMS(Location=sms[-1][0]["Location"], Folder=0) ) - remain = remain - len(sms) + remain -= len(sms) data = gammu.LinkSMS(sms) for item in data: message = gammu.DecodeSMS(item) if message is None: - self.assertEqual(item[0]["UDH"]["Type"], "NoUDH") + assert item[0]["UDH"]["Type"] == "NoUDH" - def test_todo(self): + def test_todo(self) -> None: state_machine = self.get_statemachine() status = state_machine.GetToDoStatus() remain = status["Used"] - self.assertEqual(status["Used"], 2) + assert status["Used"] == 2 start = True @@ -248,31 +242,29 @@ def test_todo(self): start = False else: entry = state_machine.GetNextToDo(Location=entry["Location"]) - remain = remain - 1 + remain -= 1 - def test_sms_folders(self): + def test_sms_folders(self) -> None: state_machine = self.get_statemachine() folders = state_machine.GetSMSFolders() - self.assertEqual(len(folders), 5) + assert len(folders) == 5 - def ussd_callback(self, state_machine, response, data): - """ - Callback on USSD data. - """ + def ussd_callback(self, state_machine, response, data) -> None: + """Callback on USSD data.""" self._called = True - self.assertEqual(response, "USSD") - self.assertEqual(data["Text"], "Reply for 1234") - self.assertEqual(data["Status"], "NoActionNeeded") + assert response == "USSD" + assert data["Text"] == "Reply for 1234" + assert data["Status"] == "NoActionNeeded" - def test_ussd(self): + def test_ussd(self) -> None: self._called = False state_machine = self.get_statemachine() state_machine.SetIncomingCallback(self.ussd_callback) state_machine.SetIncomingUSSD() state_machine.DialService("1234") - self.assertTrue(self._called) + assert self._called - def test_sendsms(self): + def test_sendsms(self) -> None: state_machine = self.get_statemachine() message = { "Text": "python-gammu testing message", @@ -282,7 +274,7 @@ def test_sendsms(self): state_machine.SendSMS(message) - def test_sendsms_long(self): + def test_sendsms_long(self) -> None: state_machine = self.get_statemachine() text = ( "Very long python-gammu testing message sent from example python script. " @@ -296,7 +288,7 @@ def test_sendsms_long(self): # Encode messages encoded = gammu.EncodeSMS(smsinfo) - self.assertEqual(len(encoded), 5) + assert len(encoded) == 5 # Send messages for message in encoded: @@ -307,53 +299,46 @@ def test_sendsms_long(self): # Actually send the message state_machine.SendSMS(message) - def test_filesystem(self): + def test_filesystem(self) -> None: state_machine = self.get_statemachine() fs_info = state_machine.GetFileSystemStatus() - self.assertEqual( - fs_info, - { - "UsedImages": 0, - "Used": 1000000, - "UsedThemes": 0, - "Free": 10101, - "UsedSounds": 0, - }, - ) + assert fs_info == { + "UsedImages": 0, + "Used": 1000000, + "UsedThemes": 0, + "Free": 10101, + "UsedSounds": 0, + } - def test_deletefile(self): + def test_deletefile(self) -> None: state_machine = self.get_statemachine() - self.assertRaises( - gammu.ERR_FILENOTEXIST, - state_machine.DeleteFile, - "testfolder/nonexisting.png", - ) + with pytest.raises(gammu.ERR_FILENOTEXIST): + state_machine.DeleteFile("testfolder/nonexisting.png") state_machine.DeleteFile("file5") - def test_deletefolder(self): + def test_deletefolder(self) -> None: state_machine = self.get_statemachine() - self.assertRaises( - gammu.ERR_FILENOTEXIST, state_machine.DeleteFolder, "testfolder" - ) + with pytest.raises(gammu.ERR_FILENOTEXIST): + state_machine.DeleteFolder("testfolder") state_machine.AddFolder("", "testfolder") state_machine.DeleteFolder("testfolder") @unittest.skipIf(platform.system() == "Windows", "Not supported on Windows") - def test_emoji_folder(self): + def test_emoji_folder(self) -> None: state_machine = self.get_statemachine() name = "test-😘" - self.assertRaises(gammu.ERR_FILENOTEXIST, state_machine.DeleteFolder, name) - self.assertEqual(name, state_machine.AddFolder("", name)) + with pytest.raises(gammu.ERR_FILENOTEXIST): + state_machine.DeleteFolder(name) + assert name == state_machine.AddFolder("", name) # Check the folder exists as expected on filesystem - self.assertTrue(os.path.exists(os.path.join(self.dummy_dir, "fs", name))) + assert os.path.exists(os.path.join(self.dummy_dir, "fs", name)) state_machine.DeleteFolder(name) - def test_addfile(self): + def test_addfile(self) -> None: state_machine = self.get_statemachine() file_stat = os.stat(TEST_FILE) ttime = datetime.datetime.fromtimestamp(file_stat[8]) - with open(TEST_FILE, "rb") as handle: - content = handle.read() + content = pathlib.Path(TEST_FILE).read_bytes() file_f = { "ID_FullName": "testfolder", "Name": "sqlite.sql", @@ -375,13 +360,13 @@ def test_addfile(self): while not file_f["Finished"]: file_f = state_machine.AddFilePart(file_f) - def test_fileattributes(self): + def test_fileattributes(self) -> None: state_machine = self.get_statemachine() state_machine.SetFileAttributes( "file5", ReadOnly=1, Protected=0, System=1, Hidden=1 ) - def test_getnextfile(self): + def test_getnextfile(self) -> None: state_machine = self.get_statemachine() file_f = state_machine.GetNextFileFolder(1) folders = 0 @@ -395,10 +380,10 @@ def test_getnextfile(self): file_f = state_machine.GetNextFileFolder(0) except gammu.ERR_EMPTY: break - self.assertEqual(folders, 3) - self.assertEqual(files, 6) + assert folders == 3 + assert files == 6 - def test_incoming_call(self): + def test_incoming_call(self) -> None: self.check_incoming_call() self._called = False state_machine = self.get_statemachine() @@ -407,4 +392,4 @@ def test_incoming_call(self): state_machine.GetSignalQuality() self.fake_incoming_call() state_machine.GetSignalQuality() - self.assertTrue(self._called) + assert self._called diff --git a/test/test_errors.py b/test/test_errors.py index c87bf201c..f185b81a5 100644 --- a/test/test_errors.py +++ b/test/test_errors.py @@ -19,17 +19,18 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # import unittest +from typing import NoReturn + +import pytest import gammu.exception -def error_function(): - raise gammu.exception.ERR_WRONGCRC() +def error_function() -> NoReturn: + raise gammu.exception.ERR_WRONGCRC class ErrorTest(unittest.TestCase): - def test_catching(self): - self.assertRaises( - gammu.exception.GSMError, - error_function, - ) + def test_catching(self) -> None: + with pytest.raises(gammu.exception.GSMError): + error_function() diff --git a/test/test_sms.py b/test/test_sms.py index a55c25c1e..ee4474d73 100644 --- a/test/test_sms.py +++ b/test/test_sms.py @@ -55,17 +55,17 @@ class PDUTest(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: if "GAMMU_DEBUG" in os.environ: gammu.SetDebugFile(sys.stderr) gammu.SetDebugLevel("textall") - def test_decode(self): + def test_decode(self) -> None: sms = gammu.DecodePDU(PDU_DATA) - self.assertEqual(sms["Number"], "604865888") - self.assertEqual(sms["Text"], "Delivered") + assert sms["Number"] == "604865888" + assert sms["Text"] == "Delivered" - def do_smstest(self, smsinfo, expected): + def do_smstest(self, smsinfo, expected) -> None: # encode SMSes sms = gammu.EncodeSMS(smsinfo) @@ -73,7 +73,7 @@ def do_smstest(self, smsinfo, expected): decodedsms = gammu.DecodeSMS(sms) # compare text - self.assertEqual(decodedsms["Entries"][0]["Buffer"], expected) + assert decodedsms["Entries"][0]["Buffer"] == expected # do conversion to PDU pdu = [gammu.EncodePDU(s) for s in sms] @@ -85,24 +85,24 @@ def do_smstest(self, smsinfo, expected): decodedsms = gammu.DecodeSMS(pdusms) # compare PDU results - self.assertEqual(decodedsms["Entries"][0]["Buffer"], expected) + assert decodedsms["Entries"][0]["Buffer"] == expected - def test_encode_plain(self): + def test_encode_plain(self) -> None: smsinfo = {"Entries": [{"ID": "ConcatenatedTextLong", "Buffer": MESSAGE}]} self.do_smstest(smsinfo, MESSAGE) - def test_encode_gsm(self): + def test_encode_gsm(self) -> None: smsinfo = {"Entries": [{"ID": "ConcatenatedTextLong", "Buffer": GSM}]} self.do_smstest(smsinfo, GSM) - def test_encode_unicode(self): + def test_encode_unicode(self) -> None: smsinfo = { "Entries": [{"ID": "ConcatenatedTextLong", "Buffer": UNICODE}], "Unicode": True, } self.do_smstest(smsinfo, UNICODE) - def test_link(self): + def test_link(self) -> None: # SMS info about message smsinfo = {"Entries": [{"ID": "ConcatenatedTextLong", "Buffer": MESSAGE}]} @@ -116,9 +116,9 @@ def test_link(self): decodedsms = gammu.DecodeSMS(linked[0]) # compare results - self.assertTrue(decodedsms["Entries"][0]["Buffer"], MESSAGE) + assert decodedsms["Entries"][0]["Buffer"], MESSAGE - def test_mms_decode(self): + def test_mms_decode(self) -> None: message = [ { "RejectDuplicates": 0, @@ -165,20 +165,15 @@ def test_mms_decode(self): ] decoded = gammu.DecodeSMS(message) - self.assertEqual( - decoded["Entries"][0]["MMSIndicator"]["Address"], - "http://mmsc.labmctel.fr:9090/m33", + assert ( + decoded["Entries"][0]["MMSIndicator"]["Address"] + == "http://mmsc.labmctel.fr:9090/m33" ) - def test_counter(self): - self.assertEqual(gammu.SMSCounter("foobar"), (1, 154)) - - def test_counter_long(self): - self.assertEqual( - gammu.SMSCounter( - "foobar fjsa;kjfkasdjfkljsklfjaskdljfkljasdfkljqilui143uu51o2" - "3rjhskdf jasdklfjasdklf jasdfkljasdlkfj;asd;lfjaskdljf431ou9" - "83jdfaskljfklsdjdkljasfl sdfjasdfkl jafklsda" - ), - (2, 156), - ) + def test_counter(self) -> None: + assert gammu.SMSCounter("foobar") == (1, 154) + + def test_counter_long(self) -> None: + assert gammu.SMSCounter( + "foobar fjsa;kjfkasdjfkljsklfjaskdljfkljasdfkljqilui143uu51o23rjhskdf jasdklfjasdklf jasdfkljasdlkfj;asd;lfjaskdljf431ou983jdfaskljfklsdjdkljasfl sdfjasdfkl jafklsda" + ) == (2, 156) diff --git a/test/test_smsd.py b/test/test_smsd.py index ab86114ef..1fdcc97d5 100644 --- a/test/test_smsd.py +++ b/test/test_smsd.py @@ -19,13 +19,14 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -import os import os.path import platform import sqlite3 import threading import time -import unittest +from pathlib import Path + +import pytest import gammu import gammu.smsd @@ -45,7 +46,8 @@ def get_script(): - """Returns SQL script to create database + """ + Returns SQL script to create database. It returns correct script matching used Gammu version. """ @@ -66,15 +68,13 @@ def get_script(): class SMSDDummyTest(DummyTest): - def setUp(self): + def setUp(self) -> None: if platform.system() == "Windows": - raise unittest.SkipTest( - "SMSD testing not supported on Windows (no DBI driver)" - ) + pytest.skip("SMSD testing not supported on Windows (no DBI driver)") super().setUp() database = sqlite3.connect(os.path.join(self.test_dir, "smsd.db")) - with open(get_script()) as handle: - database.executescript(handle.read()) + script = Path(get_script()).read_text(encoding="utf-8") + database.executescript(script) # Check if SMSD with SQLite driver is available # This will fail if Gammu was built without SQLite support @@ -90,8 +90,8 @@ def setUp(self): # If error happens during SMSD_ReadConfig, it's likely a driver/config issue # Common error codes: 27 (ERR_UNKNOWN), 75 (DB driver initialization failed) - if error_where == "SMSD_ReadConfig" or error_code in (27, 75): - raise unittest.SkipTest( + if error_where == "SMSD_ReadConfig" or error_code in {27, 75}: + pytest.skip( "SMSD initialization failed (Gammu may be built without required database driver support)" ) # Re-raise if it's a different error @@ -100,14 +100,15 @@ def setUp(self): def get_smsd(self): return gammu.smsd.SMSD(self.config_name) - def test_init_error(self): - self.assertRaises(TypeError, gammu.smsd.SMSD, Bar=1) + def test_init_error(self) -> None: + with pytest.raises(TypeError): + gammu.smsd.SMSD(Bar=1) - def test_inject(self): + def test_inject(self) -> None: smsd = self.get_smsd() smsd.InjectSMS([MESSAGE_1]) - def test_smsd(self): + def test_smsd(self) -> None: smsd = self.get_smsd() # Inject SMS messages @@ -132,17 +133,13 @@ def test_smsd(self): time.sleep(10) retries += 1 - self.assertEqual( - status["Received"], - 2, + assert status["Received"] == 2, ( "Messages were not received as expected ({:d})!".format( status["Received"] - ), + ) ) - self.assertEqual( - status["Sent"], - 2, - "Messages were not sent as expected ({:d})!".format(status["Sent"]), + assert status["Sent"] == 2, ( + "Messages were not sent as expected ({:d})!".format(status["Sent"]) ) time.sleep(1) diff --git a/test/test_unicode_surrogates.py b/test/test_unicode_surrogates.py index 36621992f..174ea0146 100644 --- a/test/test_unicode_surrogates.py +++ b/test/test_unicode_surrogates.py @@ -28,7 +28,7 @@ class SurrogateTest(unittest.TestCase): """Test handling of Unicode surrogates in PDU decoding.""" - def test_invalid_high_surrogate_alone(self): + def test_invalid_high_surrogate_alone(self) -> None: """Test that a high surrogate without a low surrogate is replaced with U+FFFD.""" # Create a PDU with an invalid high surrogate (0xD800) followed by a regular char # PDU structure: SMSC info + PDU type + sender + ... + UDH + text @@ -61,12 +61,12 @@ def test_invalid_high_surrogate_alone(self): # The invalid surrogate should be replaced with replacement character # U+FFFD (REPLACEMENT CHARACTER) - self.assertIn("\ufffd", text) + assert "�" in text except UnicodeEncodeError as e: self.fail(f"UnicodeEncodeError should not be raised: {e}") - def test_valid_surrogate_pair(self): + def test_valid_surrogate_pair(self) -> None: """Test that valid surrogate pairs are correctly decoded to supplementary characters.""" # Valid surrogate pair: D800 DC00 = U+10000 # Create a PDU with a valid surrogate pair @@ -93,12 +93,12 @@ def test_valid_surrogate_pair(self): # Valid surrogate pair D800 DC00 should decode to U+10000 # U+10000 in UTF-8 is F0 90 80 80 - self.assertIn(b"\xf0\x90\x80\x80", encoded) + assert b"\xf0\x90\x80\x80" in encoded except UnicodeEncodeError as e: self.fail(f"UnicodeEncodeError should not be raised: {e}") - def test_high_surrogate_with_invalid_low(self): + def test_high_surrogate_with_invalid_low(self) -> None: """Test that a high surrogate followed by invalid low surrogate is handled.""" # High surrogate 0xD800 followed by invalid value 0x0100 (not a low surrogate) pdu_hex = ( @@ -123,12 +123,12 @@ def test_high_surrogate_with_invalid_low(self): text.encode("utf-8") # The invalid surrogate should be replaced - self.assertIn("\ufffd", text) + assert "�" in text except UnicodeEncodeError as e: self.fail(f"UnicodeEncodeError should not be raised: {e}") - def test_low_surrogate_alone(self): + def test_low_surrogate_alone(self) -> None: """Test that a standalone low surrogate is replaced with U+FFFD.""" # Low surrogate 0xDC00 alone (without preceding high surrogate) pdu_hex = ( @@ -153,7 +153,7 @@ def test_low_surrogate_alone(self): text.encode("utf-8") # The standalone low surrogate should be replaced - self.assertIn("\ufffd", text) + assert "�" in text except UnicodeEncodeError as e: self.fail(f"UnicodeEncodeError should not be raised: {e}") diff --git a/test/test_worker.py b/test/test_worker.py index 7bdf7d89c..204608791 100644 --- a/test/test_worker.py +++ b/test/test_worker.py @@ -298,10 +298,10 @@ class WorkerDummyTest(DummyTest): results = [] - def callback(self, name, result, error, percents): + def callback(self, name, result, error, percents) -> None: self.results.append((name, result, error, percents)) - def test_worker(self): + def test_worker(self) -> None: self.results = [] worker = gammu.worker.GammuWorker(self.callback) worker.configure(self.get_statemachine().GetConfig()) @@ -337,15 +337,15 @@ def test_worker(self): # Remove GetDateTime from comparing as the value changes for i in range(len(self.results)): if self.results[i][0] == "GetDateTime": - self.assertEqual(self.results[i][2], "ERR_NONE") - self.assertEqual(self.results[i][3], 100) + assert self.results[i][2] == "ERR_NONE" + assert self.results[i][3] == 100 del self.results[i] break self.maxDiff = None - self.assertEqual(WORKER_EXPECT, self.results) + assert self.results == WORKER_EXPECT - def test_incoming(self): + def test_incoming(self) -> None: self.check_incoming_call() self.results = [] self._called = False @@ -356,4 +356,4 @@ def test_incoming(self): worker.enqueue("SetIncomingCall") self.fake_incoming_call() worker.terminate() - self.assertTrue(self._called) + assert self._called