1515import shutil
1616from pathlib import Path
1717from functools import wraps
18+ from typing import Dict , Any , Optional , Callable , Tuple , List
1819from watchdog .observers import Observer
19- from watchdog .events import FileSystemEventHandler
20+ from watchdog .events import FileSystemEventHandler , FileSystemEvent
2021
2122# Determine script directory
2223SCRIPT_DIR = Path (__file__ ).parent .resolve ()
3839RETRY_BACKOFF_MULTIPLIER = 2 # Exponential backoff multiplier
3940
4041
41- def parse_rsync_stats (stdout ) :
42+ def parse_rsync_stats (stdout : Optional [ str ]) -> Dict [ str , Any ] :
4243 """Parse rsync --stats output into a dictionary of values."""
43- stats = {}
44+ stats : Dict [ str , Any ] = {}
4445
4546 if not stdout :
4647 return stats
@@ -72,8 +73,8 @@ def parse_rsync_stats(stdout):
7273
7374 return stats
7475
75- def retry_on_failure (max_attempts = RETRY_MAX_ATTEMPTS , initial_delay = RETRY_INITIAL_DELAY ,
76- backoff_multiplier = RETRY_BACKOFF_MULTIPLIER ):
76+ def retry_on_failure (max_attempts : int = RETRY_MAX_ATTEMPTS , initial_delay : float = RETRY_INITIAL_DELAY ,
77+ backoff_multiplier : float = RETRY_BACKOFF_MULTIPLIER ) -> Callable :
7778 """
7879 Decorator to retry function on transient failures with exponential backoff.
7980
@@ -87,9 +88,9 @@ def retry_on_failure(max_attempts=RETRY_MAX_ATTEMPTS, initial_delay=RETRY_INITIA
8788 or subprocess.TimeoutExpired. On success, returns a tuple of
8889 (result, attempts_used).
8990 """
90- def decorator (func ) :
91+ def decorator (func : Callable ) -> Callable :
9192 @wraps (func )
92- def wrapper (* args , ** kwargs ) :
93+ def wrapper (* args : Any , ** kwargs : Any ) -> Tuple [ Any , int ] :
9394 attempt = 1
9495 delay = initial_delay
9596
@@ -113,7 +114,7 @@ def wrapper(*args, **kwargs):
113114 return wrapper
114115 return decorator
115116
116- def load_config ():
117+ def load_config () -> Dict [ str , str ] :
117118 """Load configuration from config.local"""
118119 config_file = SCRIPT_DIR / 'config.local'
119120
@@ -270,26 +271,26 @@ def load_config():
270271class GCodeHandler (FileSystemEventHandler ):
271272 """Handler for .gcode file events"""
272273
273- def __init__ (self ):
274+ def __init__ (self ) -> None :
274275 self .syncing = set () # Track files currently being synced
275276 self .syncing_lock = threading .Lock () # Prevent race conditions
276277
277- def on_created (self , event ) :
278+ def on_created (self , event : FileSystemEvent ) -> None :
278279 """Called when a file is created"""
279280 if not event .is_directory and event .src_path .endswith ('.gcode' ):
280281 self .sync_file (event .src_path )
281282
282- def on_moved (self , event ) :
283+ def on_moved (self , event : FileSystemEvent ) -> None :
283284 """Called when a file is moved into the directory"""
284285 if not event .is_directory and event .dest_path .endswith ('.gcode' ):
285286 self .sync_file (event .dest_path )
286287
287- def on_modified (self , event ) :
288+ def on_modified (self , event : FileSystemEvent ) -> None :
288289 """Called when a file is modified (handles saves from some editors)"""
289290 if not event .is_directory and event .src_path .endswith ('.gcode' ):
290291 self .sync_file (event .src_path )
291292
292- def sync_file (self , file_path ) :
293+ def sync_file (self , file_path : str ) -> None :
293294 """Sync a file to the remote server"""
294295 # Thread-safe check and add
295296 with self .syncing_lock :
@@ -440,7 +441,7 @@ def sync_file(self, file_path):
440441 self .syncing .discard (file_path )
441442
442443 @retry_on_failure ()
443- def _execute_rsync_with_retry (self , rsync_cmd , timeout_seconds ) :
444+ def _execute_rsync_with_retry (self , rsync_cmd : List [ str ] , timeout_seconds : int ) -> subprocess . CompletedProcess :
444445 """Execute rsync command with retry logic for transient failures.
445446
446447 Args:
@@ -464,7 +465,7 @@ def _execute_rsync_with_retry(self, rsync_cmd, timeout_seconds):
464465 )
465466
466467 @retry_on_failure (max_attempts = 3 , initial_delay = 2 , backoff_multiplier = 2 )
467- def _execute_usb_refresh_with_retry (self ):
468+ def _execute_usb_refresh_with_retry (self ) -> bool :
468469 """Execute USB refresh with subprocess (called by retry decorator).
469470
470471 Returns:
@@ -497,7 +498,7 @@ def _execute_usb_refresh_with_retry(self):
497498 logging .debug (f"Refresh output: { result .stdout .strip ()} " )
498499 return True
499500
500- def refresh_usb_gadget (self ):
501+ def refresh_usb_gadget (self ) -> bool :
501502 """Trigger USB gadget refresh on the Pi with retry logic.
502503
503504 Returns:
@@ -528,7 +529,7 @@ def refresh_usb_gadget(self):
528529 return False
529530
530531
531- def main ():
532+ def main () -> None :
532533 """Main function"""
533534 # Check if watchdog is installed
534535 try :
0 commit comments