fastled 1.1.32__py2.py3-none-any.whl → 1.1.34__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,202 +1,202 @@
1
- """File system watcher implementation using watchdog
2
- """
3
-
4
- import hashlib
5
- import os
6
- import queue
7
- import threading
8
- import time
9
- from contextlib import redirect_stdout
10
- from multiprocessing import Process, Queue
11
- from pathlib import Path
12
- from queue import Empty
13
- from typing import Dict, Set
14
-
15
- from watchdog.events import FileSystemEvent, FileSystemEventHandler
16
- from watchdog.observers import Observer
17
- from watchdog.observers.api import BaseObserver
18
-
19
-
20
- class MyEventHandler(FileSystemEventHandler):
21
- def __init__(
22
- self,
23
- change_queue: queue.Queue,
24
- excluded_patterns: Set[str],
25
- file_hashes: Dict[str, str],
26
- ) -> None:
27
- super().__init__()
28
- self.change_queue = change_queue
29
- self.excluded_patterns = excluded_patterns
30
- self.file_hashes = file_hashes
31
-
32
- def _get_file_hash(self, filepath: str) -> str:
33
- try:
34
- with open(filepath, "rb") as f:
35
- return hashlib.md5(f.read()).hexdigest()
36
- except Exception: # pylint: disable=broad-except
37
- return ""
38
-
39
- def on_modified(self, event: FileSystemEvent) -> None:
40
- if not event.is_directory:
41
- # Convert src_path to str if it's bytes
42
- src_path = (
43
- event.src_path.decode()
44
- if isinstance(event.src_path, bytes)
45
- else event.src_path
46
- )
47
- path = Path(src_path)
48
- # Check if any part of the path matches excluded patterns
49
- if not any(part in self.excluded_patterns for part in path.parts):
50
- new_hash = self._get_file_hash(src_path)
51
- if new_hash and new_hash != self.file_hashes.get(src_path):
52
- self.file_hashes[src_path] = new_hash
53
- self.change_queue.put(src_path)
54
-
55
-
56
- class FileChangedNotifier(threading.Thread):
57
- """Watches a directory for file changes and queues notifications"""
58
-
59
- def __init__(
60
- self,
61
- path: str,
62
- debounce_seconds: float = 1.0,
63
- excluded_patterns: list[str] | None = None,
64
- ) -> None:
65
- """Initialize the notifier with a path to watch
66
-
67
- Args:
68
- path: Directory path to watch for changes
69
- debounce_seconds: Minimum time between notifications for the same file
70
- excluded_patterns: List of directory/file patterns to exclude from watching
71
- """
72
- super().__init__(daemon=True)
73
- self.path = path
74
- self.observer: BaseObserver | None = None
75
- self.event_handler: MyEventHandler | None = None
76
-
77
- # Combine default and user-provided patterns
78
- self.excluded_patterns = (
79
- set(excluded_patterns) if excluded_patterns is not None else set()
80
- )
81
- self.stopped = False
82
- self.change_queue: queue.Queue = queue.Queue()
83
- self.last_notification: Dict[str, float] = {}
84
- self.file_hashes: Dict[str, str] = {}
85
- self.debounce_seconds = debounce_seconds
86
-
87
- def stop(self) -> None:
88
- """Stop watching for changes"""
89
- print("watcher stop")
90
- self.stopped = True
91
- if self.observer:
92
- self.observer.stop()
93
- self.observer.join()
94
- self.observer = None
95
- self.event_handler = None
96
-
97
- def run(self) -> None:
98
- """Thread main loop - starts watching for changes"""
99
- self.event_handler = MyEventHandler(
100
- self.change_queue, self.excluded_patterns, self.file_hashes
101
- )
102
- self.observer = Observer()
103
- self.observer.schedule(self.event_handler, self.path, recursive=True)
104
- self.observer.start()
105
-
106
- try:
107
- while not self.stopped:
108
- time.sleep(0.1)
109
- except KeyboardInterrupt:
110
- print("File watcher stopped by user.")
111
- finally:
112
- self.stop()
113
-
114
- def get_next_change(self, timeout: float = 0.001) -> str | None:
115
- """Get the next file change event from the queue
116
-
117
- Args:
118
- timeout: How long to wait for next change in seconds
119
-
120
- Returns:
121
- Changed filepath or None if no change within timeout
122
- """
123
- try:
124
- filepath = self.change_queue.get(timeout=timeout)
125
- current_time = time.time()
126
-
127
- # Check if we've seen this file recently
128
- last_time = self.last_notification.get(filepath, 0)
129
- if current_time - last_time < self.debounce_seconds:
130
- return None
131
-
132
- self.last_notification[filepath] = current_time
133
- return filepath
134
- except KeyboardInterrupt:
135
- raise
136
- except queue.Empty:
137
- return None
138
-
139
- def get_all_changes(self, timeout: float = 0.001) -> list[str]:
140
- """Get all file change events from the queue
141
-
142
- Args:
143
- timeout: How long to wait for next change in seconds
144
-
145
- Returns:
146
- List of changed filepaths
147
- """
148
- changed_files = []
149
- while True:
150
- changed_file = self.get_next_change(timeout=timeout)
151
- if changed_file is None:
152
- break
153
- changed_files.append(changed_file)
154
- # clear all the changes from the queue
155
- self.change_queue.queue.clear()
156
- return changed_files
157
-
158
-
159
- def _process_wrapper(root: Path, excluded_patterns: list[str], queue: Queue):
160
- with open(os.devnull, "w") as fnull: # Redirect to /dev/null
161
- with redirect_stdout(fnull):
162
- watcher = FileChangedNotifier(
163
- str(root), excluded_patterns=excluded_patterns
164
- )
165
- watcher.start()
166
- while True:
167
- try:
168
- changed_files = watcher.get_all_changes()
169
- for file in changed_files:
170
- queue.put(file)
171
- except KeyboardInterrupt:
172
- break
173
- watcher.stop()
174
-
175
-
176
- class FileWatcherProcess:
177
- def __init__(self, root: Path, excluded_patterns: list[str]) -> None:
178
- self.queue: Queue = Queue()
179
- self.process = Process(
180
- target=_process_wrapper,
181
- args=(root, excluded_patterns, self.queue),
182
- daemon=True,
183
- )
184
- self.process.start()
185
-
186
- def stop(self):
187
- self.process.terminate()
188
- self.process.join()
189
- self.queue.close()
190
- self.queue.join_thread()
191
-
192
- def get_all_changes(self, timeout: float | None = None) -> list[str]:
193
- changed_files = []
194
- block = timeout is not None
195
-
196
- while True:
197
- try:
198
- changed_file = self.queue.get(block=block, timeout=timeout)
199
- changed_files.append(changed_file)
200
- except Empty:
201
- break
202
- return changed_files
1
+ """File system watcher implementation using watchdog
2
+ """
3
+
4
+ import hashlib
5
+ import os
6
+ import queue
7
+ import threading
8
+ import time
9
+ from contextlib import redirect_stdout
10
+ from multiprocessing import Process, Queue
11
+ from pathlib import Path
12
+ from queue import Empty
13
+ from typing import Dict, Set
14
+
15
+ from watchdog.events import FileSystemEvent, FileSystemEventHandler
16
+ from watchdog.observers import Observer
17
+ from watchdog.observers.api import BaseObserver
18
+
19
+
20
+ class MyEventHandler(FileSystemEventHandler):
21
+ def __init__(
22
+ self,
23
+ change_queue: queue.Queue,
24
+ excluded_patterns: Set[str],
25
+ file_hashes: Dict[str, str],
26
+ ) -> None:
27
+ super().__init__()
28
+ self.change_queue = change_queue
29
+ self.excluded_patterns = excluded_patterns
30
+ self.file_hashes = file_hashes
31
+
32
+ def _get_file_hash(self, filepath: str) -> str:
33
+ try:
34
+ with open(filepath, "rb") as f:
35
+ return hashlib.md5(f.read()).hexdigest()
36
+ except Exception: # pylint: disable=broad-except
37
+ return ""
38
+
39
+ def on_modified(self, event: FileSystemEvent) -> None:
40
+ if not event.is_directory:
41
+ # Convert src_path to str if it's bytes
42
+ src_path = (
43
+ event.src_path.decode()
44
+ if isinstance(event.src_path, bytes)
45
+ else event.src_path
46
+ )
47
+ path = Path(src_path)
48
+ # Check if any part of the path matches excluded patterns
49
+ if not any(part in self.excluded_patterns for part in path.parts):
50
+ new_hash = self._get_file_hash(src_path)
51
+ if new_hash and new_hash != self.file_hashes.get(src_path):
52
+ self.file_hashes[src_path] = new_hash
53
+ self.change_queue.put(src_path)
54
+
55
+
56
+ class FileChangedNotifier(threading.Thread):
57
+ """Watches a directory for file changes and queues notifications"""
58
+
59
+ def __init__(
60
+ self,
61
+ path: str,
62
+ debounce_seconds: float = 1.0,
63
+ excluded_patterns: list[str] | None = None,
64
+ ) -> None:
65
+ """Initialize the notifier with a path to watch
66
+
67
+ Args:
68
+ path: Directory path to watch for changes
69
+ debounce_seconds: Minimum time between notifications for the same file
70
+ excluded_patterns: List of directory/file patterns to exclude from watching
71
+ """
72
+ super().__init__(daemon=True)
73
+ self.path = path
74
+ self.observer: BaseObserver | None = None
75
+ self.event_handler: MyEventHandler | None = None
76
+
77
+ # Combine default and user-provided patterns
78
+ self.excluded_patterns = (
79
+ set(excluded_patterns) if excluded_patterns is not None else set()
80
+ )
81
+ self.stopped = False
82
+ self.change_queue: queue.Queue = queue.Queue()
83
+ self.last_notification: Dict[str, float] = {}
84
+ self.file_hashes: Dict[str, str] = {}
85
+ self.debounce_seconds = debounce_seconds
86
+
87
+ def stop(self) -> None:
88
+ """Stop watching for changes"""
89
+ print("watcher stop")
90
+ self.stopped = True
91
+ if self.observer:
92
+ self.observer.stop()
93
+ self.observer.join()
94
+ self.observer = None
95
+ self.event_handler = None
96
+
97
+ def run(self) -> None:
98
+ """Thread main loop - starts watching for changes"""
99
+ self.event_handler = MyEventHandler(
100
+ self.change_queue, self.excluded_patterns, self.file_hashes
101
+ )
102
+ self.observer = Observer()
103
+ self.observer.schedule(self.event_handler, self.path, recursive=True)
104
+ self.observer.start()
105
+
106
+ try:
107
+ while not self.stopped:
108
+ time.sleep(0.1)
109
+ except KeyboardInterrupt:
110
+ print("File watcher stopped by user.")
111
+ finally:
112
+ self.stop()
113
+
114
+ def get_next_change(self, timeout: float = 0.001) -> str | None:
115
+ """Get the next file change event from the queue
116
+
117
+ Args:
118
+ timeout: How long to wait for next change in seconds
119
+
120
+ Returns:
121
+ Changed filepath or None if no change within timeout
122
+ """
123
+ try:
124
+ filepath = self.change_queue.get(timeout=timeout)
125
+ current_time = time.time()
126
+
127
+ # Check if we've seen this file recently
128
+ last_time = self.last_notification.get(filepath, 0)
129
+ if current_time - last_time < self.debounce_seconds:
130
+ return None
131
+
132
+ self.last_notification[filepath] = current_time
133
+ return filepath
134
+ except KeyboardInterrupt:
135
+ raise
136
+ except queue.Empty:
137
+ return None
138
+
139
+ def get_all_changes(self, timeout: float = 0.001) -> list[str]:
140
+ """Get all file change events from the queue
141
+
142
+ Args:
143
+ timeout: How long to wait for next change in seconds
144
+
145
+ Returns:
146
+ List of changed filepaths
147
+ """
148
+ changed_files = []
149
+ while True:
150
+ changed_file = self.get_next_change(timeout=timeout)
151
+ if changed_file is None:
152
+ break
153
+ changed_files.append(changed_file)
154
+ # clear all the changes from the queue
155
+ self.change_queue.queue.clear()
156
+ return changed_files
157
+
158
+
159
+ def _process_wrapper(root: Path, excluded_patterns: list[str], queue: Queue):
160
+ with open(os.devnull, "w") as fnull: # Redirect to /dev/null
161
+ with redirect_stdout(fnull):
162
+ watcher = FileChangedNotifier(
163
+ str(root), excluded_patterns=excluded_patterns
164
+ )
165
+ watcher.start()
166
+ while True:
167
+ try:
168
+ changed_files = watcher.get_all_changes()
169
+ for file in changed_files:
170
+ queue.put(file)
171
+ except KeyboardInterrupt:
172
+ break
173
+ watcher.stop()
174
+
175
+
176
+ class FileWatcherProcess:
177
+ def __init__(self, root: Path, excluded_patterns: list[str]) -> None:
178
+ self.queue: Queue = Queue()
179
+ self.process = Process(
180
+ target=_process_wrapper,
181
+ args=(root, excluded_patterns, self.queue),
182
+ daemon=True,
183
+ )
184
+ self.process.start()
185
+
186
+ def stop(self):
187
+ self.process.terminate()
188
+ self.process.join()
189
+ self.queue.close()
190
+ self.queue.join_thread()
191
+
192
+ def get_all_changes(self, timeout: float | None = None) -> list[str]:
193
+ changed_files = []
194
+ block = timeout is not None
195
+
196
+ while True:
197
+ try:
198
+ changed_file = self.queue.get(block=block, timeout=timeout)
199
+ changed_files.append(changed_file)
200
+ except Empty:
201
+ break
202
+ return changed_files
fastled/open_browser.py CHANGED
@@ -1,59 +1,59 @@
1
- import os
2
- import socket
3
- import sys
4
- from multiprocessing import Process
5
- from pathlib import Path
6
-
7
- from livereload import Server
8
-
9
- DEFAULT_PORT = 8081
10
-
11
-
12
- def _open_browser_python(fastled_js: Path, port: int) -> Server:
13
- """Start livereload server in the fastled_js directory using API"""
14
- print(f"\nStarting livereload server in {fastled_js} on port {port}")
15
-
16
- # server = Server()
17
- # server.watch(str(fastled_js / "index.html"), delay=0.1)
18
- # server.setHeader("Cache-Control", "no-cache")
19
- # server.serve(root=str(fastled_js), port=port, open_url_delay=0.5)
20
- # return server
21
- os.system(f"cd {fastled_js} && live-server")
22
-
23
-
24
- def _find_open_port(start_port: int) -> int:
25
- """Find an open port starting from start_port."""
26
- port = start_port
27
- while True:
28
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
29
- if sock.connect_ex(("localhost", port)) != 0:
30
- return port
31
- port += 1
32
-
33
-
34
- def _run_server(fastled_js: Path, port: int) -> None:
35
- """Function to run in separate process that starts the livereload server"""
36
- sys.stderr = open(os.devnull, "w") # Suppress stderr output
37
- _open_browser_python(fastled_js, port)
38
- try:
39
- # Keep the process running
40
- while True:
41
- pass
42
- except KeyboardInterrupt:
43
- print("\nShutting down livereload server...")
44
-
45
-
46
- def open_browser_process(fastled_js: Path, port: int | None = None) -> Process:
47
- """Start livereload server in the fastled_js directory and return the process"""
48
- if port is None:
49
- port = DEFAULT_PORT
50
-
51
- port = _find_open_port(port)
52
-
53
- process = Process(
54
- target=_run_server,
55
- args=(fastled_js, port),
56
- daemon=True,
57
- )
58
- process.start()
59
- return process
1
+ import os
2
+ import socket
3
+ import sys
4
+ from multiprocessing import Process
5
+ from pathlib import Path
6
+
7
+ from livereload import Server
8
+
9
+ DEFAULT_PORT = 8081
10
+
11
+
12
+ def _open_browser_python(fastled_js: Path, port: int) -> Server:
13
+ """Start livereload server in the fastled_js directory using API"""
14
+ print(f"\nStarting livereload server in {fastled_js} on port {port}")
15
+
16
+ # server = Server()
17
+ # server.watch(str(fastled_js / "index.html"), delay=0.1)
18
+ # server.setHeader("Cache-Control", "no-cache")
19
+ # server.serve(root=str(fastled_js), port=port, open_url_delay=0.5)
20
+ # return server
21
+ os.system(f"cd {fastled_js} && live-server")
22
+
23
+
24
+ def _find_open_port(start_port: int) -> int:
25
+ """Find an open port starting from start_port."""
26
+ port = start_port
27
+ while True:
28
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
29
+ if sock.connect_ex(("localhost", port)) != 0:
30
+ return port
31
+ port += 1
32
+
33
+
34
+ def _run_server(fastled_js: Path, port: int) -> None:
35
+ """Function to run in separate process that starts the livereload server"""
36
+ sys.stderr = open(os.devnull, "w") # Suppress stderr output
37
+ _open_browser_python(fastled_js, port)
38
+ try:
39
+ # Keep the process running
40
+ while True:
41
+ pass
42
+ except KeyboardInterrupt:
43
+ print("\nShutting down livereload server...")
44
+
45
+
46
+ def open_browser_process(fastled_js: Path, port: int | None = None) -> Process:
47
+ """Start livereload server in the fastled_js directory and return the process"""
48
+ if port is None:
49
+ port = DEFAULT_PORT
50
+
51
+ port = _find_open_port(port)
52
+
53
+ process = Process(
54
+ target=_run_server,
55
+ args=(fastled_js, port),
56
+ daemon=True,
57
+ )
58
+ process.start()
59
+ return process
fastled/parse_args.py CHANGED
@@ -122,13 +122,20 @@ def parse_args() -> argparse.Namespace:
122
122
  and not args.web
123
123
  and not args.server
124
124
  ):
125
- # print(f"Using web compiler at {DEFAULT_URL}")
126
- args.web = DEFAULT_URL
127
125
  if DockerManager.is_docker_installed():
128
- print("Docker is installed. Use --server to run the compiler locally.")
129
- args.localhost = True
126
+ if not DockerManager.ensure_linux_containers_for_windows():
127
+ print(
128
+ f"Windows must be in linux containers mode, but is in Windows container mode, Using web compiler at {DEFAULT_URL}."
129
+ )
130
+ args.web = DEFAULT_URL
131
+ else:
132
+ print(
133
+ "Docker is installed. Defaulting to --local mode, use --web to override and use the web compiler instead."
134
+ )
135
+ args.localhost = True
130
136
  else:
131
- print("Docker is not installed. Using web compiler.")
137
+ print(f"Docker is not installed. Using web compiler at {DEFAULT_URL}.")
138
+ args.web = DEFAULT_URL
132
139
  if cwd_is_fastled and not args.web and not args.server:
133
140
  print("Forcing --local mode because we are in the FastLED repo")
134
141
  args.localhost = True
fastled/spinner.py CHANGED
@@ -1,34 +1,34 @@
1
- import _thread
2
- import threading
3
- import time
4
- import warnings
5
-
6
- from progress.spinner import Spinner as SpinnerImpl
7
-
8
-
9
- class Spinner:
10
- def __init__(self, message: str = ""):
11
- self.spinner = SpinnerImpl(message)
12
- self.event = threading.Event()
13
- self.thread = threading.Thread(target=self._spin, daemon=True)
14
- self.thread.start()
15
-
16
- def _spin(self) -> None:
17
- try:
18
- while not self.event.is_set():
19
- self.spinner.next()
20
- time.sleep(0.1)
21
- except KeyboardInterrupt:
22
- _thread.interrupt_main()
23
- except Exception as e:
24
- warnings.warn(f"Spinner thread failed: {e}")
25
-
26
- def stop(self) -> None:
27
- self.event.set()
28
- self.thread.join()
29
-
30
- def __enter__(self):
31
- return self
32
-
33
- def __exit__(self, exc_type, exc_val, exc_tb):
34
- self.stop()
1
+ import _thread
2
+ import threading
3
+ import time
4
+ import warnings
5
+
6
+ from progress.spinner import Spinner as SpinnerImpl
7
+
8
+
9
+ class Spinner:
10
+ def __init__(self, message: str = ""):
11
+ self.spinner = SpinnerImpl(message)
12
+ self.event = threading.Event()
13
+ self.thread = threading.Thread(target=self._spin, daemon=True)
14
+ self.thread.start()
15
+
16
+ def _spin(self) -> None:
17
+ try:
18
+ while not self.event.is_set():
19
+ self.spinner.next()
20
+ time.sleep(0.1)
21
+ except KeyboardInterrupt:
22
+ _thread.interrupt_main()
23
+ except Exception as e:
24
+ warnings.warn(f"Spinner thread failed: {e}")
25
+
26
+ def stop(self) -> None:
27
+ self.event.set()
28
+ self.thread.join()
29
+
30
+ def __enter__(self):
31
+ return self
32
+
33
+ def __exit__(self, exc_type, exc_val, exc_tb):
34
+ self.stop()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastled
3
- Version: 1.1.32
3
+ Version: 1.1.34
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies
@@ -163,6 +163,8 @@ A: A big chunk of space is being used by unnecessary javascript `emscripten` is
163
163
 
164
164
  # Revisions
165
165
 
166
+ * 1.1.34 - On windows check to make sure we are in linux container mode, if not try to switch and if that fails then use `--web` compiler.
167
+ * 1.1.33 - Auto updating frequency has been reduced from one hour to one day. To update immediatly use `--update`.
166
168
  * 1.1.32 - `--init` now asks for which example you want, then tells you where the example was downloaded to. No longer auto-compiles.
167
169
  * 1.1.31 - `--local` is auto-enabled if docker is installed, use `--web` to force web compiler. Updating is much more pretty.
168
170
  * 1.1.30 - Added `--init` to initialize a demo project.