fastled 1.0.8__py2.py3-none-any.whl → 1.0.10__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fastled/filewatcher.py CHANGED
@@ -1,146 +1,169 @@
1
- """File system watcher implementation using watchdog
2
- """
3
-
4
- import hashlib
5
- import queue
6
- import threading
7
- import time
8
- from pathlib import Path
9
- from typing import Dict, Set
10
-
11
- from watchdog.events import FileSystemEvent, FileSystemEventHandler
12
- from watchdog.observers import Observer
13
- from watchdog.observers.api import BaseObserver
14
-
15
-
16
- class MyEventHandler(FileSystemEventHandler):
17
- def __init__(
18
- self,
19
- change_queue: queue.Queue,
20
- excluded_patterns: Set[str],
21
- file_hashes: Dict[str, str],
22
- ) -> None:
23
- super().__init__()
24
- self.change_queue = change_queue
25
- self.excluded_patterns = excluded_patterns
26
- self.file_hashes = file_hashes
27
-
28
- def _get_file_hash(self, filepath: str) -> str:
29
- try:
30
- with open(filepath, "rb") as f:
31
- return hashlib.md5(f.read()).hexdigest()
32
- except Exception: # pylint: disable=broad-except
33
- return ""
34
-
35
- def on_modified(self, event: FileSystemEvent) -> None:
36
- if not event.is_directory:
37
- path = Path(event.src_path)
38
- # Check if any part of the path matches excluded patterns
39
- if not any(part in self.excluded_patterns for part in path.parts):
40
- new_hash = self._get_file_hash(event.src_path)
41
- if new_hash and new_hash != self.file_hashes.get(event.src_path):
42
- self.file_hashes[event.src_path] = new_hash
43
- self.change_queue.put(event.src_path)
44
-
45
-
46
- class FileChangedNotifier(threading.Thread):
47
- """Watches a directory for file changes and queues notifications"""
48
-
49
- def __init__(
50
- self,
51
- path: str,
52
- debounce_seconds: float = 1.0,
53
- excluded_patterns: list[str] | None = None,
54
- ) -> None:
55
- """Initialize the notifier with a path to watch
56
-
57
- Args:
58
- path: Directory path to watch for changes
59
- debounce_seconds: Minimum time between notifications for the same file
60
- excluded_patterns: List of directory/file patterns to exclude from watching
61
- """
62
- super().__init__(daemon=True)
63
- self.path = path
64
- self.observer: BaseObserver | None = None
65
- self.event_handler: MyEventHandler | None = None
66
-
67
- # Combine default and user-provided patterns
68
- self.excluded_patterns = (
69
- set(excluded_patterns) if excluded_patterns is not None else set()
70
- )
71
- self.stopped = False
72
- self.change_queue: queue.Queue = queue.Queue()
73
- self.last_notification: Dict[str, float] = {}
74
- self.file_hashes: Dict[str, str] = {}
75
- self.debounce_seconds = debounce_seconds
76
-
77
- def stop(self) -> None:
78
- """Stop watching for changes"""
79
- print("watcher stop")
80
- self.stopped = True
81
- if self.observer:
82
- self.observer.stop()
83
- self.observer.join()
84
- self.observer = None
85
- self.event_handler = None
86
-
87
- def run(self) -> None:
88
- """Thread main loop - starts watching for changes"""
89
- self.event_handler = MyEventHandler(
90
- self.change_queue, self.excluded_patterns, self.file_hashes
91
- )
92
- self.observer = Observer()
93
- self.observer.schedule(self.event_handler, self.path, recursive=True)
94
- self.observer.start()
95
-
96
- try:
97
- while not self.stopped:
98
- time.sleep(0.1)
99
- except KeyboardInterrupt:
100
- print("File watcher stopped by user.")
101
- finally:
102
- self.stop()
103
-
104
- def get_next_change(self, timeout: float = 0.001) -> str | None:
105
- """Get the next file change event from the queue
106
-
107
- Args:
108
- timeout: How long to wait for next change in seconds
109
-
110
- Returns:
111
- Changed filepath or None if no change within timeout
112
- """
113
- try:
114
- filepath = self.change_queue.get(timeout=timeout)
115
- current_time = time.time()
116
-
117
- # Check if we've seen this file recently
118
- last_time = self.last_notification.get(filepath, 0)
119
- if current_time - last_time < self.debounce_seconds:
120
- return None
121
-
122
- self.last_notification[filepath] = current_time
123
- return filepath
124
- except KeyboardInterrupt:
125
- raise
126
- except queue.Empty:
127
- return None
128
-
129
- def get_all_changes(self, timeout: float = 0.001) -> list[str]:
130
- """Get all file change events from the queue
131
-
132
- Args:
133
- timeout: How long to wait for next change in seconds
134
-
135
- Returns:
136
- List of changed filepaths
137
- """
138
- changed_files = []
139
- while True:
140
- changed_file = self.get_next_change(timeout=timeout)
141
- if changed_file is None:
142
- break
143
- changed_files.append(changed_file)
144
- # clear all the changes from the queue
145
- self.change_queue.queue.clear()
146
- return changed_files
1
+ """File system watcher implementation using watchdog
2
+ """
3
+
4
+ import hashlib
5
+ import queue
6
+ import threading
7
+ import time
8
+ from multiprocessing import Process, Queue
9
+ from pathlib import Path
10
+ from typing import Dict, Set
11
+
12
+ from watchdog.events import FileSystemEvent, FileSystemEventHandler
13
+ from watchdog.observers import Observer
14
+ from watchdog.observers.api import BaseObserver
15
+
16
+
17
+ class MyEventHandler(FileSystemEventHandler):
18
+ def __init__(
19
+ self,
20
+ change_queue: queue.Queue,
21
+ excluded_patterns: Set[str],
22
+ file_hashes: Dict[str, str],
23
+ ) -> None:
24
+ super().__init__()
25
+ self.change_queue = change_queue
26
+ self.excluded_patterns = excluded_patterns
27
+ self.file_hashes = file_hashes
28
+
29
+ def _get_file_hash(self, filepath: str) -> str:
30
+ try:
31
+ with open(filepath, "rb") as f:
32
+ return hashlib.md5(f.read()).hexdigest()
33
+ except Exception: # pylint: disable=broad-except
34
+ return ""
35
+
36
+ def on_modified(self, event: FileSystemEvent) -> None:
37
+ if not event.is_directory:
38
+ path = Path(event.src_path)
39
+ # Check if any part of the path matches excluded patterns
40
+ if not any(part in self.excluded_patterns for part in path.parts):
41
+ new_hash = self._get_file_hash(event.src_path)
42
+ if new_hash and new_hash != self.file_hashes.get(event.src_path):
43
+ self.file_hashes[event.src_path] = new_hash
44
+ self.change_queue.put(event.src_path)
45
+
46
+
47
+ class FileChangedNotifier(threading.Thread):
48
+ """Watches a directory for file changes and queues notifications"""
49
+
50
+ def __init__(
51
+ self,
52
+ path: str,
53
+ debounce_seconds: float = 1.0,
54
+ excluded_patterns: list[str] | None = None,
55
+ ) -> None:
56
+ """Initialize the notifier with a path to watch
57
+
58
+ Args:
59
+ path: Directory path to watch for changes
60
+ debounce_seconds: Minimum time between notifications for the same file
61
+ excluded_patterns: List of directory/file patterns to exclude from watching
62
+ """
63
+ super().__init__(daemon=True)
64
+ self.path = path
65
+ self.observer: BaseObserver | None = None
66
+ self.event_handler: MyEventHandler | None = None
67
+
68
+ # Combine default and user-provided patterns
69
+ self.excluded_patterns = (
70
+ set(excluded_patterns) if excluded_patterns is not None else set()
71
+ )
72
+ self.stopped = False
73
+ self.change_queue: queue.Queue = queue.Queue()
74
+ self.last_notification: Dict[str, float] = {}
75
+ self.file_hashes: Dict[str, str] = {}
76
+ self.debounce_seconds = debounce_seconds
77
+
78
+ def stop(self) -> None:
79
+ """Stop watching for changes"""
80
+ print("watcher stop")
81
+ self.stopped = True
82
+ if self.observer:
83
+ self.observer.stop()
84
+ self.observer.join()
85
+ self.observer = None
86
+ self.event_handler = None
87
+
88
+ def run(self) -> None:
89
+ """Thread main loop - starts watching for changes"""
90
+ self.event_handler = MyEventHandler(
91
+ self.change_queue, self.excluded_patterns, self.file_hashes
92
+ )
93
+ self.observer = Observer()
94
+ self.observer.schedule(self.event_handler, self.path, recursive=True)
95
+ self.observer.start()
96
+
97
+ try:
98
+ while not self.stopped:
99
+ time.sleep(0.1)
100
+ except KeyboardInterrupt:
101
+ print("File watcher stopped by user.")
102
+ finally:
103
+ self.stop()
104
+
105
+ def get_next_change(self, timeout: float = 0.001) -> str | None:
106
+ """Get the next file change event from the queue
107
+
108
+ Args:
109
+ timeout: How long to wait for next change in seconds
110
+
111
+ Returns:
112
+ Changed filepath or None if no change within timeout
113
+ """
114
+ try:
115
+ filepath = self.change_queue.get(timeout=timeout)
116
+ current_time = time.time()
117
+
118
+ # Check if we've seen this file recently
119
+ last_time = self.last_notification.get(filepath, 0)
120
+ if current_time - last_time < self.debounce_seconds:
121
+ return None
122
+
123
+ self.last_notification[filepath] = current_time
124
+ return filepath
125
+ except KeyboardInterrupt:
126
+ raise
127
+ except queue.Empty:
128
+ return None
129
+
130
+ def get_all_changes(self, timeout: float = 0.001) -> list[str]:
131
+ """Get all file change events from the queue
132
+
133
+ Args:
134
+ timeout: How long to wait for next change in seconds
135
+
136
+ Returns:
137
+ List of changed filepaths
138
+ """
139
+ changed_files = []
140
+ while True:
141
+ changed_file = self.get_next_change(timeout=timeout)
142
+ if changed_file is None:
143
+ break
144
+ changed_files.append(changed_file)
145
+ # clear all the changes from the queue
146
+ self.change_queue.queue.clear()
147
+ return changed_files
148
+
149
+
150
+ def _process_wrapper(root: Path, excluded_patterns: list[str], queue: Queue):
151
+ watcher = FileChangedNotifier(str(root), excluded_patterns=excluded_patterns)
152
+ watcher.start()
153
+ while True:
154
+ try:
155
+ changed_files = watcher.get_all_changes()
156
+ for file in changed_files:
157
+ queue.put(file)
158
+ except KeyboardInterrupt:
159
+ break
160
+ watcher.stop()
161
+
162
+
163
+ def create_file_watcher_process(
164
+ root: Path, excluded_patterns: list[str]
165
+ ) -> tuple[Process, Queue]:
166
+ """Create a new process running the file watcher"""
167
+ queue: Queue = Queue()
168
+ process = Process(target=_process_wrapper, args=(root, excluded_patterns, queue))
169
+ return process, queue
fastled/util.py ADDED
@@ -0,0 +1,10 @@
1
+ import hashlib
2
+ from pathlib import Path
3
+
4
+
5
+ def hash_file(file_path: Path) -> str:
6
+ hasher = hashlib.sha256()
7
+ with open(file_path, "rb") as f:
8
+ for chunk in iter(lambda: f.read(4096), b""):
9
+ hasher.update(chunk)
10
+ return hasher.hexdigest()