fastled 1.1.45__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/__init__.py ADDED
@@ -0,0 +1,128 @@
1
+ """FastLED Wasm Compiler package."""
2
+
3
+ # context
4
+ from contextlib import contextmanager
5
+ from pathlib import Path
6
+ from typing import Generator
7
+
8
+ from .compile_server import CompileServer
9
+ from .live_client import LiveClient
10
+ from .types import BuildMode, CompileResult, CompileServerError
11
+
12
+ __version__ = "1.1.45"
13
+
14
+
15
+ class Api:
16
+ @staticmethod
17
+ def get_examples():
18
+ from fastled.project_init import get_examples
19
+
20
+ return get_examples()
21
+
22
+ @staticmethod
23
+ def project_init(
24
+ example=None, outputdir=None, host: str | CompileServer | None = None
25
+ ) -> Path:
26
+ from fastled.project_init import project_init
27
+
28
+ if isinstance(host, CompileServer):
29
+ host = host.url()
30
+ return project_init(example, outputdir, host)
31
+
32
+ @staticmethod
33
+ def web_compile(
34
+ directory: Path | str,
35
+ host: str | CompileServer | None = None,
36
+ build_mode: BuildMode = BuildMode.QUICK,
37
+ profile: bool = False, # When true then profile information will be enabled and included in the zip.
38
+ ) -> CompileResult:
39
+ from fastled.web_compile import web_compile
40
+
41
+ if isinstance(host, CompileServer):
42
+ host = host.url()
43
+ if isinstance(directory, str):
44
+ directory = Path(directory)
45
+ out: CompileResult = web_compile(
46
+ directory, host, build_mode=build_mode, profile=profile
47
+ )
48
+ return out
49
+
50
+ @staticmethod
51
+ def live_client(
52
+ sketch_directory: Path,
53
+ host: str | CompileServer | None = None,
54
+ auto_start=True,
55
+ open_web_browser=True,
56
+ keep_running=True,
57
+ build_mode=BuildMode.QUICK,
58
+ profile=False,
59
+ ) -> LiveClient:
60
+ return LiveClient(
61
+ sketch_directory=sketch_directory,
62
+ host=host,
63
+ auto_start=auto_start,
64
+ open_web_browser=open_web_browser,
65
+ keep_running=keep_running,
66
+ build_mode=build_mode,
67
+ profile=profile,
68
+ )
69
+
70
+ @staticmethod
71
+ def spawn_server(
72
+ interactive=False,
73
+ auto_updates=None,
74
+ auto_start=True,
75
+ container_name: str | None = None,
76
+ ) -> CompileServer:
77
+ from fastled.compile_server import CompileServer
78
+
79
+ out = CompileServer(
80
+ container_name=container_name,
81
+ interactive=interactive,
82
+ auto_updates=auto_updates,
83
+ mapped_dir=None,
84
+ auto_start=auto_start,
85
+ )
86
+ return out
87
+
88
+ @staticmethod
89
+ @contextmanager
90
+ def server(
91
+ interactive=False,
92
+ auto_updates=None,
93
+ auto_start=True,
94
+ container_name: str | None = None,
95
+ ) -> Generator[CompileServer, None, None]:
96
+ server = Api.spawn_server(
97
+ interactive=interactive,
98
+ auto_updates=auto_updates,
99
+ auto_start=auto_start,
100
+ container_name=container_name,
101
+ )
102
+ try:
103
+ yield server
104
+ finally:
105
+ server.stop()
106
+
107
+
108
+ class Test:
109
+ @staticmethod
110
+ def test_examples(
111
+ examples: list[str] | None = None, host: str | CompileServer | None = None
112
+ ) -> dict[str, Exception]:
113
+ from fastled.test.examples import test_examples
114
+
115
+ if isinstance(host, CompileServer):
116
+ host = host.url()
117
+
118
+ return test_examples(examples=examples, host=host)
119
+
120
+
121
+ __all__ = [
122
+ "Api",
123
+ "Test",
124
+ "CompileServer",
125
+ "CompileResult",
126
+ "CompileServerError",
127
+ "BuildMode",
128
+ ]
fastled/app.py ADDED
@@ -0,0 +1,67 @@
1
+ """
2
+ Uses the latest wasm compiler image to compile the FastLED sketch.
3
+ """
4
+
5
+ import argparse
6
+ import sys
7
+ import time
8
+ from pathlib import Path
9
+
10
+ from fastled.client_server import run_client_server
11
+ from fastled.compile_server import CompileServer
12
+ from fastled.parse_args import parse_args
13
+
14
+
15
+ def run_server(args: argparse.Namespace) -> int:
16
+ interactive = args.interactive
17
+ auto_update = args.auto_update
18
+ mapped_dir = Path(args.directory).absolute() if args.directory else None
19
+ compile_server = CompileServer(
20
+ interactive=interactive,
21
+ auto_updates=auto_update,
22
+ mapped_dir=mapped_dir,
23
+ auto_start=True,
24
+ )
25
+
26
+ if not interactive:
27
+ print(f"Server started at {compile_server.url()}")
28
+ try:
29
+ while True:
30
+ if not compile_server.process_running():
31
+ print("Server process is not running. Exiting...")
32
+ return 1
33
+ time.sleep(0.1)
34
+ except KeyboardInterrupt:
35
+ print("\nExiting from server...")
36
+ return 1
37
+ finally:
38
+ compile_server.stop()
39
+ return 0
40
+
41
+
42
+ def main() -> int:
43
+ args = parse_args()
44
+ if args.update:
45
+ # Force auto_update to ensure update check happens
46
+ compile_server = CompileServer(interactive=False, auto_updates=True)
47
+ compile_server.stop()
48
+ print("Finished updating.")
49
+ return 0
50
+
51
+ if args.server:
52
+ print("Running in server only mode.")
53
+ return run_server(args)
54
+ else:
55
+ print("Running in client/server mode.")
56
+ return run_client_server(args)
57
+
58
+
59
+ if __name__ == "__main__":
60
+ try:
61
+ sys.exit(main())
62
+ except KeyboardInterrupt:
63
+ print("\nExiting from main...")
64
+ sys.exit(1)
65
+ except Exception as e:
66
+ print(f"Error: {e}")
67
+ sys.exit(1)
@@ -0,0 +1 @@
1
+ Example assets that will be deployed with python code.
fastled/cli.py ADDED
@@ -0,0 +1,16 @@
1
+ """
2
+ Main entry point.
3
+ """
4
+
5
+ import sys
6
+
7
+ from fastled.app import main as app_main
8
+
9
+
10
+ def main() -> int:
11
+ """Main entry point for the template_python_cmd package."""
12
+ return app_main()
13
+
14
+
15
+ if __name__ == "__main__":
16
+ sys.exit(main())
@@ -0,0 +1,351 @@
1
+ import argparse
2
+ import shutil
3
+ import tempfile
4
+ import threading
5
+ import time
6
+ from multiprocessing import Process
7
+ from pathlib import Path
8
+
9
+ from fastled.compile_server import CompileServer
10
+ from fastled.docker_manager import DockerManager
11
+ from fastled.filewatcher import FileWatcherProcess
12
+ from fastled.keyboard import SpaceBarWatcher
13
+ from fastled.open_browser import open_browser_process
14
+ from fastled.settings import DEFAULT_URL
15
+ from fastled.sketch import looks_like_sketch_directory
16
+ from fastled.types import BuildMode, CompileResult, CompileServerError
17
+ from fastled.web_compile import (
18
+ SERVER_PORT,
19
+ ConnectionResult,
20
+ find_good_connection,
21
+ web_compile,
22
+ )
23
+
24
+
25
+ # Override this function in your own code to run tests before compilation
26
+ def TEST_BEFORE_COMPILE(url) -> None:
27
+ pass
28
+
29
+
30
+ def _run_web_compiler(
31
+ directory: Path,
32
+ host: str,
33
+ build_mode: BuildMode,
34
+ profile: bool,
35
+ last_hash_value: str | None,
36
+ ) -> CompileResult:
37
+ input_dir = Path(directory)
38
+ output_dir = input_dir / "fastled_js"
39
+ start = time.time()
40
+ web_result = web_compile(
41
+ directory=input_dir, host=host, build_mode=build_mode, profile=profile
42
+ )
43
+ diff = time.time() - start
44
+ if not web_result.success:
45
+ print("\nWeb compilation failed:")
46
+ print(f"Time taken: {diff:.2f} seconds")
47
+ print(web_result.stdout)
48
+ return web_result
49
+
50
+ def print_results() -> None:
51
+ hash_value = (
52
+ web_result.hash_value
53
+ if web_result.hash_value is not None
54
+ else "NO HASH VALUE"
55
+ )
56
+ print(
57
+ f"\nWeb compilation successful\n Time: {diff:.2f}\n output: {output_dir}\n hash: {hash_value}\n zip size: {len(web_result.zip_bytes)} bytes"
58
+ )
59
+
60
+ # now check to see if the hash value is the same as the last hash value
61
+ if last_hash_value is not None and last_hash_value == web_result.hash_value:
62
+ print("\nSkipping redeploy: No significant changes found.")
63
+ print_results()
64
+ return web_result
65
+
66
+ # Extract zip contents to fastled_js directory
67
+ output_dir.mkdir(exist_ok=True)
68
+ with tempfile.TemporaryDirectory() as temp_dir:
69
+ temp_path = Path(temp_dir)
70
+ temp_zip = temp_path / "result.zip"
71
+ temp_zip.write_bytes(web_result.zip_bytes)
72
+
73
+ # Clear existing contents
74
+ shutil.rmtree(output_dir, ignore_errors=True)
75
+ output_dir.mkdir(exist_ok=True)
76
+
77
+ # Extract zip contents
78
+ shutil.unpack_archive(temp_zip, output_dir, "zip")
79
+
80
+ print(web_result.stdout)
81
+ print_results()
82
+ return web_result
83
+
84
+
85
+ def _try_start_server_or_get_url(
86
+ auto_update: bool, args_web: str | bool, localhost: bool
87
+ ) -> tuple[str, CompileServer | None]:
88
+ is_local_host = localhost or (
89
+ isinstance(args_web, str)
90
+ and ("localhost" in args_web or "127.0.0.1" in args_web)
91
+ )
92
+ # test to see if there is already a local host server
93
+ local_host_needs_server = False
94
+ if is_local_host:
95
+ addr = "localhost" if localhost or not isinstance(args_web, str) else args_web
96
+ urls = [addr]
97
+ if ":" not in addr:
98
+ urls.append(f"{addr}:{SERVER_PORT}")
99
+
100
+ result: ConnectionResult | None = find_good_connection(urls)
101
+ if result is not None:
102
+ print(f"Found local server at {result.host}")
103
+ return (result.host, None)
104
+ else:
105
+ local_host_needs_server = True
106
+
107
+ if not local_host_needs_server and args_web:
108
+ if isinstance(args_web, str):
109
+ return (args_web, None)
110
+ if isinstance(args_web, bool):
111
+ return (DEFAULT_URL, None)
112
+ return (args_web, None)
113
+ else:
114
+ try:
115
+ print("No local server found, starting one...")
116
+ compile_server = CompileServer(auto_updates=auto_update)
117
+ print("Waiting for the local compiler to start...")
118
+ if not compile_server.ping():
119
+ print("Failed to start local compiler.")
120
+ raise CompileServerError("Failed to start local compiler.")
121
+ return (compile_server.url(), compile_server)
122
+ except KeyboardInterrupt:
123
+ raise
124
+ except RuntimeError:
125
+ print("Failed to start local compile server, using web compiler instead.")
126
+ return (DEFAULT_URL, None)
127
+
128
+
129
+ def run_client(
130
+ directory: Path,
131
+ host: str | CompileServer | None,
132
+ open_web_browser: bool = True,
133
+ keep_running: bool = True, # if false, only one compilation will be done.
134
+ build_mode: BuildMode = BuildMode.QUICK,
135
+ profile: bool = False,
136
+ shutdown: threading.Event | None = None,
137
+ ) -> int:
138
+
139
+ compile_server: CompileServer | None = (
140
+ host if isinstance(host, CompileServer) else None
141
+ )
142
+ shutdown = shutdown or threading.Event()
143
+
144
+ def get_url() -> str:
145
+ if compile_server is not None:
146
+ return compile_server.url()
147
+ if isinstance(host, str):
148
+ return host
149
+ return DEFAULT_URL
150
+
151
+ url = get_url()
152
+
153
+ try:
154
+
155
+ def compile_function(
156
+ url: str = url,
157
+ build_mode: BuildMode = build_mode,
158
+ profile: bool = profile,
159
+ last_hash_value: str | None = None,
160
+ ) -> CompileResult:
161
+ TEST_BEFORE_COMPILE(url)
162
+ return _run_web_compiler(
163
+ directory,
164
+ host=url,
165
+ build_mode=build_mode,
166
+ profile=profile,
167
+ last_hash_value=last_hash_value,
168
+ )
169
+
170
+ result: CompileResult = compile_function(last_hash_value=None)
171
+ last_compiled_result: CompileResult = result
172
+
173
+ if not result.success:
174
+ print("\nCompilation failed.")
175
+
176
+ browser_proc: Process | None = None
177
+ if open_web_browser:
178
+ browser_proc = open_browser_process(directory / "fastled_js")
179
+ else:
180
+ print("\nCompilation successful.")
181
+ if compile_server:
182
+ print("Shutting down compile server...")
183
+ compile_server.stop()
184
+ return 0
185
+
186
+ if not keep_running or shutdown.is_set():
187
+ if browser_proc:
188
+ browser_proc.kill()
189
+ return 0 if result.success else 1
190
+ except KeyboardInterrupt:
191
+ print("\nExiting from main")
192
+ return 1
193
+
194
+ sketch_filewatcher = FileWatcherProcess(directory, excluded_patterns=["fastled_js"])
195
+
196
+ source_code_watcher: FileWatcherProcess | None = None
197
+ if compile_server and compile_server.using_fastled_src_dir_volume():
198
+ assert compile_server.fastled_src_dir is not None
199
+ source_code_watcher = FileWatcherProcess(
200
+ compile_server.fastled_src_dir, excluded_patterns=[]
201
+ )
202
+
203
+ def trigger_rebuild_if_sketch_changed(
204
+ last_compiled_result: CompileResult,
205
+ ) -> tuple[bool, CompileResult]:
206
+ changed_files = sketch_filewatcher.get_all_changes()
207
+ if changed_files:
208
+ print(f"\nChanges detected in {changed_files}")
209
+ last_hash_value = last_compiled_result.hash_value
210
+ out = compile_function(last_hash_value=last_hash_value)
211
+ if not out.success:
212
+ print("\nRecompilation failed.")
213
+ else:
214
+ print("\nRecompilation successful.")
215
+ return True, out
216
+ return False, last_compiled_result
217
+
218
+ def print_status() -> None:
219
+ print("Will compile on sketch changes or if you hit the space bar.")
220
+
221
+ print_status()
222
+ print("Press Ctrl+C to stop...")
223
+
224
+ try:
225
+ while True:
226
+ if shutdown.is_set():
227
+ print("\nStopping watch mode...")
228
+ return 0
229
+ if SpaceBarWatcher.watch_space_bar_pressed(timeout=1.0):
230
+ print("Compiling...")
231
+ last_compiled_result = compile_function(last_hash_value=None)
232
+ if not last_compiled_result.success:
233
+ print("\nRecompilation failed.")
234
+ else:
235
+ print("\nRecompilation successful.")
236
+ # drain the space bar queue
237
+ SpaceBarWatcher.watch_space_bar_pressed()
238
+ print_status()
239
+ continue
240
+ changed, last_compiled_result = trigger_rebuild_if_sketch_changed(
241
+ last_compiled_result
242
+ )
243
+ if changed:
244
+ print_status()
245
+ continue
246
+ if compile_server and not compile_server.process_running():
247
+ print("Server process is not running. Exiting...")
248
+ return 1
249
+ if source_code_watcher is not None:
250
+ changed_files = source_code_watcher.get_all_changes()
251
+ # de-duplicate changes
252
+ changed_files = sorted(list(set(changed_files)))
253
+ if changed_files:
254
+ print(f"\nChanges detected in FastLED source code: {changed_files}")
255
+ print("Press space bar to trigger compile.")
256
+ while True:
257
+ space_bar_pressed = SpaceBarWatcher.watch_space_bar_pressed(
258
+ timeout=1.0
259
+ )
260
+ file_changes = source_code_watcher.get_all_changes()
261
+ sketch_files_changed = sketch_filewatcher.get_all_changes()
262
+
263
+ if file_changes:
264
+ print(
265
+ f"Changes detected in {file_changes}\nHit the space bar to trigger compile."
266
+ )
267
+
268
+ if space_bar_pressed or sketch_files_changed:
269
+ if space_bar_pressed:
270
+ print("Space bar pressed, triggering recompile...")
271
+ elif sketch_files_changed:
272
+ print(
273
+ f"Changes detected in {','.join(sketch_files_changed)}, triggering recompile..."
274
+ )
275
+ last_compiled_result = compile_function(
276
+ last_hash_value=None
277
+ )
278
+ print("Finished recompile.")
279
+ # Drain the space bar queue
280
+ SpaceBarWatcher.watch_space_bar_pressed()
281
+ print_status()
282
+ continue
283
+
284
+ except KeyboardInterrupt:
285
+ print("\nStopping watch mode...")
286
+ return 0
287
+ except Exception as e:
288
+ print(f"Error: {e}")
289
+ return 1
290
+ finally:
291
+ sketch_filewatcher.stop()
292
+ if compile_server:
293
+ compile_server.stop()
294
+ if browser_proc:
295
+ browser_proc.kill()
296
+
297
+
298
+ def run_client_server(args: argparse.Namespace) -> int:
299
+ profile = bool(args.profile)
300
+ web: str | bool = args.web if isinstance(args.web, str) else bool(args.web)
301
+ auto_update = bool(args.auto_update)
302
+ localhost = bool(args.localhost)
303
+ directory = Path(args.directory)
304
+ just_compile = bool(args.just_compile)
305
+ interactive = bool(args.interactive)
306
+ force_compile = bool(args.force_compile)
307
+ open_web_browser = not just_compile and not interactive
308
+ build_mode: BuildMode = BuildMode.from_args(args)
309
+
310
+ if not force_compile and not looks_like_sketch_directory(directory):
311
+ print(
312
+ "Error: Not a valid FastLED sketch directory, if you are sure it is, use --force-compile"
313
+ )
314
+ return 1
315
+
316
+ # If not explicitly using web compiler, check Docker installation
317
+ if not web and not DockerManager.is_docker_installed():
318
+ print(
319
+ "\nDocker is not installed on this system - switching to web compiler instead."
320
+ )
321
+ web = True
322
+
323
+ url: str
324
+ compile_server: CompileServer | None = None
325
+ try:
326
+ url, compile_server = _try_start_server_or_get_url(auto_update, web, localhost)
327
+ except KeyboardInterrupt:
328
+ print("\nExiting from first try...")
329
+ if compile_server:
330
+ compile_server.stop()
331
+ return 1
332
+ except Exception as e:
333
+ print(f"Error: {e}")
334
+ if compile_server:
335
+ compile_server.stop()
336
+ return 1
337
+
338
+ try:
339
+ return run_client(
340
+ directory=directory,
341
+ host=compile_server if compile_server else url,
342
+ open_web_browser=open_web_browser,
343
+ keep_running=not just_compile,
344
+ build_mode=build_mode,
345
+ profile=profile,
346
+ )
347
+ except KeyboardInterrupt:
348
+ return 1
349
+ finally:
350
+ if compile_server:
351
+ compile_server.stop()
@@ -0,0 +1,87 @@
1
+ from pathlib import Path
2
+
3
+ from fastled.types import BuildMode, CompileResult, Platform
4
+
5
+
6
+ class CompileServer:
7
+
8
+ # May throw CompileServerError if auto_start is True.
9
+ def __init__(
10
+ self,
11
+ interactive: bool = False,
12
+ auto_updates: bool | None = None,
13
+ mapped_dir: Path | None = None,
14
+ auto_start: bool = True,
15
+ container_name: str | None = None,
16
+ platform: Platform = Platform.WASM,
17
+ ) -> None:
18
+ from fastled.compile_server_impl import ( # avoid circular import
19
+ CompileServerImpl,
20
+ )
21
+
22
+ assert platform == Platform.WASM, "Only WASM platform is supported right now."
23
+
24
+ self.impl = CompileServerImpl(
25
+ container_name=container_name,
26
+ interactive=interactive,
27
+ auto_updates=auto_updates,
28
+ mapped_dir=mapped_dir,
29
+ auto_start=auto_start,
30
+ )
31
+
32
+ # May throw CompileServerError if server could not be started.
33
+ def start(self, wait_for_startup=True) -> None:
34
+ # from fastled.compile_server_impl import CompileServerImpl # avoid circular import
35
+ self.impl.start(wait_for_startup=wait_for_startup)
36
+
37
+ def web_compile(
38
+ self,
39
+ directory: Path | str,
40
+ build_mode: BuildMode = BuildMode.QUICK,
41
+ profile: bool = False,
42
+ ) -> CompileResult:
43
+ return self.impl.web_compile(
44
+ directory=directory, build_mode=build_mode, profile=profile
45
+ )
46
+
47
+ def project_init(
48
+ self, example: str | None = None, outputdir: Path | None = None
49
+ ) -> None:
50
+ from fastled.project_init import project_init # avoid circular import
51
+
52
+ project_init(example=example, outputdir=outputdir)
53
+
54
+ @property
55
+ def running(self) -> bool:
56
+ return self.impl.running
57
+
58
+ @property
59
+ def fastled_src_dir(self) -> Path | None:
60
+ return self.impl.fastled_src_dir
61
+
62
+ def using_fastled_src_dir_volume(self) -> bool:
63
+ return self.impl.using_fastled_src_dir_volume()
64
+
65
+ def port(self) -> int:
66
+ return self.impl.port()
67
+
68
+ def url(self) -> str:
69
+ return self.impl.url()
70
+
71
+ def ping(self) -> bool:
72
+ return self.impl.ping()
73
+
74
+ # by default this is automatically called by the constructor, unless
75
+ # auto_start is set to False.
76
+ def wait_for_startup(self, timeout: int = 100) -> bool:
77
+ """Wait for the server to start up."""
78
+ return self.impl.wait_for_startup(timeout=timeout)
79
+
80
+ def _start(self) -> int:
81
+ return self.impl._start()
82
+
83
+ def stop(self) -> None:
84
+ return self.impl.stop()
85
+
86
+ def process_running(self) -> bool:
87
+ return self.impl.process_running()