fastled 1.1.37__py2.py3-none-any.whl → 1.1.39__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/__init__.py CHANGED
@@ -3,11 +3,13 @@
3
3
  # context
4
4
  from contextlib import contextmanager
5
5
  from pathlib import Path
6
+ from typing import Generator
6
7
 
7
8
  from .compile_server import CompileServer
8
- from .types import WebCompileResult
9
+ from .live_client import LiveClient
10
+ from .types import BuildMode, CompileResult, CompileServerError
9
11
 
10
- __version__ = "1.1.37"
12
+ __version__ = "1.1.39"
11
13
 
12
14
 
13
15
  class Api:
@@ -29,17 +31,42 @@ class Api:
29
31
 
30
32
  @staticmethod
31
33
  def web_compile(
32
- directory: Path | str, host: str | CompileServer | None = None
33
- ) -> WebCompileResult:
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:
34
39
  from fastled.web_compile import web_compile
35
40
 
36
41
  if isinstance(host, CompileServer):
37
42
  host = host.url()
38
43
  if isinstance(directory, str):
39
44
  directory = Path(directory)
40
- out: WebCompileResult = web_compile(directory, host)
45
+ out: CompileResult = web_compile(
46
+ directory, host, build_mode=build_mode, profile=profile
47
+ )
41
48
  return out
42
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
+
43
70
  @staticmethod
44
71
  def spawn_server(
45
72
  sketch_directory: Path | None = None,
@@ -47,7 +74,7 @@ class Api:
47
74
  auto_updates=None,
48
75
  auto_start=True,
49
76
  container_name: str | None = None,
50
- ):
77
+ ) -> CompileServer:
51
78
  from fastled.compile_server import CompileServer
52
79
 
53
80
  out = CompileServer(
@@ -67,7 +94,7 @@ class Api:
67
94
  auto_updates=None,
68
95
  auto_start=True,
69
96
  container_name: str | None = None,
70
- ):
97
+ ) -> Generator[CompileServer, None, None]:
71
98
  server = Api.spawn_server(
72
99
  sketch_directory=sketch_directory,
73
100
  interactive=interactive,
@@ -92,3 +119,13 @@ class Test:
92
119
  host = host.url()
93
120
 
94
121
  return test_examples(examples=examples, host=host)
122
+
123
+
124
+ __all__ = [
125
+ "Api",
126
+ "Test",
127
+ "CompileServer",
128
+ "CompileResult",
129
+ "CompileServerError",
130
+ "BuildMode",
131
+ ]
fastled/client_server.py CHANGED
@@ -1,11 +1,11 @@
1
1
  import argparse
2
2
  import shutil
3
3
  import tempfile
4
+ import threading
4
5
  import time
5
6
  from multiprocessing import Process
6
7
  from pathlib import Path
7
8
 
8
- from fastled.build_mode import BuildMode, get_build_mode
9
9
  from fastled.compile_server import CompileServer
10
10
  from fastled.docker_manager import DockerManager
11
11
  from fastled.filewatcher import FileWatcherProcess
@@ -13,9 +13,7 @@ from fastled.keyboard import SpaceBarWatcher
13
13
  from fastled.open_browser import open_browser_process
14
14
  from fastled.settings import DEFAULT_URL
15
15
  from fastled.sketch import looks_like_sketch_directory
16
-
17
- # CompiledResult
18
- from fastled.types import CompiledResult
16
+ from fastled.types import BuildMode, CompileResult, CompileServerError
19
17
  from fastled.web_compile import (
20
18
  SERVER_PORT,
21
19
  ConnectionResult,
@@ -30,7 +28,7 @@ def _run_web_compiler(
30
28
  build_mode: BuildMode,
31
29
  profile: bool,
32
30
  last_hash_value: str | None,
33
- ) -> CompiledResult:
31
+ ) -> CompileResult:
34
32
  input_dir = Path(directory)
35
33
  output_dir = input_dir / "fastled_js"
36
34
  start = time.time()
@@ -42,7 +40,7 @@ def _run_web_compiler(
42
40
  print("\nWeb compilation failed:")
43
41
  print(f"Time taken: {diff:.2f} seconds")
44
42
  print(web_result.stdout)
45
- return CompiledResult(success=False, fastled_js="", hash_value=None)
43
+ return web_result
46
44
 
47
45
  def print_results() -> None:
48
46
  hash_value = (
@@ -58,9 +56,7 @@ def _run_web_compiler(
58
56
  if last_hash_value is not None and last_hash_value == web_result.hash_value:
59
57
  print("\nSkipping redeploy: No significant changes found.")
60
58
  print_results()
61
- return CompiledResult(
62
- success=True, fastled_js=str(output_dir), hash_value=web_result.hash_value
63
- )
59
+ return web_result
64
60
 
65
61
  # Extract zip contents to fastled_js directory
66
62
  output_dir.mkdir(exist_ok=True)
@@ -78,18 +74,20 @@ def _run_web_compiler(
78
74
 
79
75
  print(web_result.stdout)
80
76
  print_results()
81
- return CompiledResult(
82
- success=True, fastled_js=str(output_dir), hash_value=web_result.hash_value
83
- )
77
+ return web_result
84
78
 
85
79
 
86
- def _try_start_server_or_get_url(args: argparse.Namespace) -> str | CompileServer:
87
- auto_update = args.auto_update
88
- is_local_host = "localhost" in args.web or "127.0.0.1" in args.web or args.localhost
80
+ def _try_start_server_or_get_url(
81
+ auto_update: bool, args_web: str | bool, localhost: bool
82
+ ) -> tuple[str, CompileServer | None]:
83
+ is_local_host = localhost or (
84
+ isinstance(args_web, str)
85
+ and ("localhost" in args_web or "127.0.0.1" in args_web)
86
+ )
89
87
  # test to see if there is already a local host server
90
88
  local_host_needs_server = False
91
89
  if is_local_host:
92
- addr = "localhost" if args.localhost else args.web
90
+ addr = "localhost" if localhost or not isinstance(args_web, str) else args_web
93
91
  urls = [addr]
94
92
  if ":" not in addr:
95
93
  urls.append(f"{addr}:{SERVER_PORT}")
@@ -97,16 +95,16 @@ def _try_start_server_or_get_url(args: argparse.Namespace) -> str | CompileServe
97
95
  result: ConnectionResult | None = find_good_connection(urls)
98
96
  if result is not None:
99
97
  print(f"Found local server at {result.host}")
100
- return result.host
98
+ return (result.host, None)
101
99
  else:
102
100
  local_host_needs_server = True
103
101
 
104
- if not local_host_needs_server and args.web:
105
- if isinstance(args.web, str):
106
- return args.web
107
- if isinstance(args.web, bool):
108
- return DEFAULT_URL
109
- return args.web
102
+ if not local_host_needs_server and args_web:
103
+ if isinstance(args_web, str):
104
+ return (args_web, None)
105
+ if isinstance(args_web, bool):
106
+ return (DEFAULT_URL, None)
107
+ return (args_web, None)
110
108
  else:
111
109
  try:
112
110
  print("No local server found, starting one...")
@@ -114,76 +112,64 @@ def _try_start_server_or_get_url(args: argparse.Namespace) -> str | CompileServe
114
112
  print("Waiting for the local compiler to start...")
115
113
  if not compile_server.ping():
116
114
  print("Failed to start local compiler.")
117
- raise RuntimeError("Failed to start local compiler.")
118
- return compile_server
115
+ raise CompileServerError("Failed to start local compiler.")
116
+ return (compile_server.url(), compile_server)
119
117
  except KeyboardInterrupt:
120
118
  raise
121
119
  except RuntimeError:
122
120
  print("Failed to start local compile server, using web compiler instead.")
123
- return DEFAULT_URL
121
+ return (DEFAULT_URL, None)
124
122
 
125
123
 
126
- def run_client_server(args: argparse.Namespace) -> int:
127
- compile_server: CompileServer | None = None
128
- open_web_browser = not args.just_compile and not args.interactive
129
- profile = args.profile
130
- if not args.force_compile and not looks_like_sketch_directory(Path(args.directory)):
131
- print(
132
- "Error: Not a valid FastLED sketch directory, if you are sure it is, use --force-compile"
133
- )
134
- return 1
124
+ def run_client(
125
+ directory: Path,
126
+ host: str | CompileServer | None,
127
+ open_web_browser: bool = True,
128
+ keep_running: bool = True, # if false, only one compilation will be done.
129
+ build_mode: BuildMode = BuildMode.QUICK,
130
+ profile: bool = False,
131
+ shutdown: threading.Event | None = None,
132
+ ) -> int:
133
+
134
+ compile_server: CompileServer | None = (
135
+ host if isinstance(host, CompileServer) else None
136
+ )
137
+ shutdown = shutdown or threading.Event()
135
138
 
136
- # If not explicitly using web compiler, check Docker installation
137
- if not args.web and not DockerManager.is_docker_installed():
138
- print(
139
- "\nDocker is not installed on this system - switching to web compiler instead."
140
- )
141
- args.web = True
139
+ def get_url() -> str:
140
+ if compile_server is not None:
141
+ return compile_server.url()
142
+ if isinstance(host, str):
143
+ return host
144
+ return DEFAULT_URL
145
+
146
+ url = get_url()
142
147
 
143
- url: str
144
148
  try:
145
- try:
146
- url_or_server: str | CompileServer = _try_start_server_or_get_url(args)
147
- if isinstance(url_or_server, str):
148
- print(f"Found URL: {url_or_server}")
149
- url = url_or_server
150
- else:
151
- compile_server = url_or_server
152
- print(f"Server started at {compile_server.url()}")
153
- url = compile_server.url()
154
- except KeyboardInterrupt:
155
- print("\nExiting from first try...")
156
- if compile_server:
157
- compile_server.stop()
158
- return 1
159
- except Exception as e:
160
- print(f"Error: {e}")
161
- return 1
162
- build_mode: BuildMode = get_build_mode(args)
163
149
 
164
150
  def compile_function(
165
151
  url: str = url,
166
152
  build_mode: BuildMode = build_mode,
167
153
  profile: bool = profile,
168
154
  last_hash_value: str | None = None,
169
- ) -> CompiledResult:
155
+ ) -> CompileResult:
170
156
  return _run_web_compiler(
171
- args.directory,
157
+ directory,
172
158
  host=url,
173
159
  build_mode=build_mode,
174
160
  profile=profile,
175
161
  last_hash_value=last_hash_value,
176
162
  )
177
163
 
178
- result: CompiledResult = compile_function(last_hash_value=None)
179
- last_compiled_result: CompiledResult = result
164
+ result: CompileResult = compile_function(last_hash_value=None)
165
+ last_compiled_result: CompileResult = result
180
166
 
181
167
  if not result.success:
182
168
  print("\nCompilation failed.")
183
169
 
184
170
  browser_proc: Process | None = None
185
171
  if open_web_browser:
186
- browser_proc = open_browser_process(Path(args.directory) / "fastled_js")
172
+ browser_proc = open_browser_process(directory / "fastled_js")
187
173
  else:
188
174
  print("\nCompilation successful.")
189
175
  if compile_server:
@@ -191,21 +177,15 @@ def run_client_server(args: argparse.Namespace) -> int:
191
177
  compile_server.stop()
192
178
  return 0
193
179
 
194
- if args.just_compile:
195
- if compile_server:
196
- compile_server.stop()
180
+ if not keep_running or shutdown.is_set():
197
181
  if browser_proc:
198
182
  browser_proc.kill()
199
183
  return 0 if result.success else 1
200
184
  except KeyboardInterrupt:
201
185
  print("\nExiting from main")
202
- if compile_server:
203
- compile_server.stop()
204
186
  return 1
205
187
 
206
- sketch_filewatcher = FileWatcherProcess(
207
- args.directory, excluded_patterns=["fastled_js"]
208
- )
188
+ sketch_filewatcher = FileWatcherProcess(directory, excluded_patterns=["fastled_js"])
209
189
 
210
190
  source_code_watcher: FileWatcherProcess | None = None
211
191
  if compile_server and compile_server.using_fastled_src_dir_volume():
@@ -215,8 +195,8 @@ def run_client_server(args: argparse.Namespace) -> int:
215
195
  )
216
196
 
217
197
  def trigger_rebuild_if_sketch_changed(
218
- last_compiled_result: CompiledResult,
219
- ) -> tuple[bool, CompiledResult]:
198
+ last_compiled_result: CompileResult,
199
+ ) -> tuple[bool, CompileResult]:
220
200
  changed_files = sketch_filewatcher.get_all_changes()
221
201
  if changed_files:
222
202
  print(f"\nChanges detected in {changed_files}")
@@ -237,6 +217,9 @@ def run_client_server(args: argparse.Namespace) -> int:
237
217
 
238
218
  try:
239
219
  while True:
220
+ if shutdown.is_set():
221
+ print("\nStopping watch mode...")
222
+ return 0
240
223
  if SpaceBarWatcher.watch_space_bar_pressed(timeout=1.0):
241
224
  print("Compiling...")
242
225
  last_compiled_result = compile_function(last_hash_value=None)
@@ -304,3 +287,59 @@ def run_client_server(args: argparse.Namespace) -> int:
304
287
  compile_server.stop()
305
288
  if browser_proc:
306
289
  browser_proc.kill()
290
+
291
+
292
+ def run_client_server(args: argparse.Namespace) -> int:
293
+ profile = bool(args.profile)
294
+ web: str | bool = args.web if isinstance(args.web, str) else bool(args.web)
295
+ auto_update = bool(args.auto_update)
296
+ localhost = bool(args.localhost)
297
+ directory = Path(args.directory)
298
+ just_compile = bool(args.just_compile)
299
+ interactive = bool(args.interactive)
300
+ force_compile = bool(args.force_compile)
301
+ open_web_browser = not just_compile and not interactive
302
+ build_mode: BuildMode = BuildMode.from_args(args)
303
+
304
+ if not force_compile and not looks_like_sketch_directory(directory):
305
+ print(
306
+ "Error: Not a valid FastLED sketch directory, if you are sure it is, use --force-compile"
307
+ )
308
+ return 1
309
+
310
+ # If not explicitly using web compiler, check Docker installation
311
+ if not web and not DockerManager.is_docker_installed():
312
+ print(
313
+ "\nDocker is not installed on this system - switching to web compiler instead."
314
+ )
315
+ web = True
316
+
317
+ url: str
318
+ compile_server: CompileServer | None = None
319
+ try:
320
+ url, compile_server = _try_start_server_or_get_url(auto_update, web, localhost)
321
+ except KeyboardInterrupt:
322
+ print("\nExiting from first try...")
323
+ if compile_server:
324
+ compile_server.stop()
325
+ return 1
326
+ except Exception as e:
327
+ print(f"Error: {e}")
328
+ if compile_server:
329
+ compile_server.stop()
330
+ return 1
331
+
332
+ try:
333
+ return run_client(
334
+ directory=directory,
335
+ host=compile_server if compile_server else url,
336
+ open_web_browser=open_web_browser,
337
+ keep_running=not just_compile,
338
+ build_mode=build_mode,
339
+ profile=profile,
340
+ )
341
+ except KeyboardInterrupt:
342
+ return 1
343
+ finally:
344
+ if compile_server:
345
+ compile_server.stop()
fastled/compile_server.py CHANGED
@@ -1,10 +1,11 @@
1
1
  from pathlib import Path
2
2
 
3
- from fastled.build_mode import BuildMode
4
- from fastled.types import WebCompileResult
3
+ from fastled.types import BuildMode, CompileResult, Platform
5
4
 
6
5
 
7
6
  class CompileServer:
7
+
8
+ # May throw CompileServerError if auto_start is True.
8
9
  def __init__(
9
10
  self,
10
11
  interactive: bool = False,
@@ -12,11 +13,14 @@ class CompileServer:
12
13
  mapped_dir: Path | None = None,
13
14
  auto_start: bool = True,
14
15
  container_name: str | None = None,
16
+ platform: Platform = Platform.WASM,
15
17
  ) -> None:
16
18
  from fastled.compile_server_impl import ( # avoid circular import
17
19
  CompileServerImpl,
18
20
  )
19
21
 
22
+ assert platform == Platform.WASM, "Only WASM platform is supported right now."
23
+
20
24
  self.impl = CompileServerImpl(
21
25
  container_name=container_name,
22
26
  interactive=interactive,
@@ -25,6 +29,7 @@ class CompileServer:
25
29
  auto_start=auto_start,
26
30
  )
27
31
 
32
+ # May throw CompileServerError if server could not be started.
28
33
  def start(self, wait_for_startup=True) -> None:
29
34
  # from fastled.compile_server_impl import CompileServerImpl # avoid circular import
30
35
  self.impl.start(wait_for_startup=wait_for_startup)
@@ -34,7 +39,7 @@ class CompileServer:
34
39
  directory: Path | str,
35
40
  build_mode: BuildMode = BuildMode.QUICK,
36
41
  profile: bool = False,
37
- ) -> WebCompileResult:
42
+ ) -> CompileResult:
38
43
  return self.impl.web_compile(
39
44
  directory=directory, build_mode=build_mode, profile=profile
40
45
  )
@@ -6,7 +6,6 @@ from pathlib import Path
6
6
 
7
7
  import httpx
8
8
 
9
- from fastled.build_mode import BuildMode
10
9
  from fastled.docker_manager import (
11
10
  DISK_CACHE,
12
11
  Container,
@@ -15,13 +14,27 @@ from fastled.docker_manager import (
15
14
  )
16
15
  from fastled.settings import SERVER_PORT
17
16
  from fastled.sketch import looks_like_fastled_repo
18
- from fastled.types import WebCompileResult
17
+ from fastled.types import BuildMode, CompileResult, CompileServerError
19
18
 
20
19
  _IMAGE_NAME = "niteris/fastled-wasm"
21
20
  _DEFAULT_CONTAINER_NAME = "fastled-wasm-compiler"
22
21
 
23
22
 
24
- SERVER_OPTIONS = ["--allow-shutdown", "--no-auto-update"]
23
+ SERVER_OPTIONS = [
24
+ "--allow-shutdown", # Allow the server to be shut down without a force kill.
25
+ "--no-auto-update", # Don't auto live updates from the git repo.
26
+ ]
27
+
28
+
29
+ def _try_get_fastled_src(path: Path) -> Path | None:
30
+ fastled_src_dir: Path | None = None
31
+ if looks_like_fastled_repo(path):
32
+ print(
33
+ "Looks like a FastLED repo, using it as the source directory and mapping it into the server."
34
+ )
35
+ fastled_src_dir = path / "src"
36
+ return fastled_src_dir
37
+ return None
25
38
 
26
39
 
27
40
  class CompileServerImpl:
@@ -40,52 +53,46 @@ class CompileServerImpl:
40
53
  )
41
54
  if not interactive and mapped_dir:
42
55
  raise ValueError("Mapped directory is only used in interactive mode")
43
- cwd = Path(".").resolve()
44
- fastled_src_dir: Path | None = None
45
- if looks_like_fastled_repo(cwd):
46
- print(
47
- "Looks like a FastLED repo, using it as the source directory and mapping it into the server."
48
- )
49
- fastled_src_dir = cwd / "src"
50
-
51
56
  self.container_name = container_name
52
57
  self.mapped_dir = mapped_dir
53
58
  self.docker = DockerManager()
54
- self.fastled_src_dir: Path | None = fastled_src_dir
59
+ self.fastled_src_dir: Path | None = _try_get_fastled_src(Path(".").resolve())
55
60
  self.interactive = interactive
56
61
  self.running_container: RunningContainer | None = None
57
62
  self.auto_updates = auto_updates
58
- # self._port = self._start()
59
63
  self._port = 0 # 0 until compile server is started
60
- # fancy print
61
- if not interactive:
62
- msg = f"# FastLED Compile Server started at {self.url()} #"
63
- print("\n" + "#" * len(msg))
64
- print(msg)
65
- print("#" * len(msg) + "\n")
66
64
  if auto_start:
67
65
  self.start()
68
66
 
69
67
  def start(self, wait_for_startup=True) -> None:
68
+ if not DockerManager.is_docker_installed():
69
+ raise CompileServerError("Docker is not installed")
70
70
  if self._port != 0:
71
71
  warnings.warn("Server has already been started")
72
72
  self._port = self._start()
73
73
  if wait_for_startup:
74
- self.wait_for_startup()
74
+ ok = self.wait_for_startup()
75
+ if not ok:
76
+ raise CompileServerError("Server did not start")
77
+ if not self.interactive:
78
+ msg = f"# FastLED Compile Server started at {self.url()} #"
79
+ print("\n" + "#" * len(msg))
80
+ print(msg)
81
+ print("#" * len(msg) + "\n")
75
82
 
76
83
  def web_compile(
77
84
  self,
78
85
  directory: Path | str,
79
86
  build_mode: BuildMode = BuildMode.QUICK,
80
87
  profile: bool = False,
81
- ) -> WebCompileResult:
88
+ ) -> CompileResult:
82
89
  from fastled.web_compile import web_compile # avoid circular import
83
90
 
84
91
  if not self._port:
85
92
  raise RuntimeError("Server has not been started yet")
86
93
  if not self.ping():
87
94
  raise RuntimeError("Server is not running")
88
- out: WebCompileResult = web_compile(
95
+ out: CompileResult = web_compile(
89
96
  directory, host=self.url(), build_mode=build_mode, profile=profile
90
97
  )
91
98
  return out
fastled/live_client.py ADDED
@@ -0,0 +1,69 @@
1
+ import threading
2
+ from pathlib import Path
3
+
4
+ from fastled.compile_server import CompileServer
5
+ from fastled.types import BuildMode
6
+
7
+
8
+ class LiveClient:
9
+ """LiveClient class watches for changes and auto-triggeres rebuild."""
10
+
11
+ def __init__(
12
+ self,
13
+ sketch_directory: Path,
14
+ host: str | CompileServer | None = None,
15
+ auto_start: bool = True,
16
+ open_web_browser: bool = True,
17
+ keep_running: bool = True,
18
+ build_mode: BuildMode = BuildMode.QUICK,
19
+ profile: bool = False,
20
+ ) -> None:
21
+ self.sketch_directory = sketch_directory
22
+ self.host = host
23
+ self.open_web_browser = open_web_browser
24
+ self.keep_running = keep_running
25
+ self.build_mode = build_mode
26
+ self.profile = profile
27
+ self.auto_start = auto_start
28
+ self.shutdown = threading.Event()
29
+ self.thread: threading.Thread | None = None
30
+ if auto_start:
31
+ self.start()
32
+
33
+ def run(self) -> int:
34
+ """Run the client."""
35
+ from fastled.client_server import run_client # avoid circular import
36
+
37
+ rtn = run_client(
38
+ directory=self.sketch_directory,
39
+ host=self.host,
40
+ open_web_browser=self.open_web_browser,
41
+ keep_running=self.keep_running,
42
+ build_mode=self.build_mode,
43
+ profile=self.profile,
44
+ shutdown=self.shutdown,
45
+ )
46
+ return rtn
47
+
48
+ @property
49
+ def running(self) -> bool:
50
+ return self.thread is not None and self.thread.is_alive()
51
+
52
+ def start(self) -> None:
53
+ """Start the client."""
54
+ assert not self.running, "LiveClient is already running"
55
+ self.shutdown.clear()
56
+ self.thread = threading.Thread(target=self.run, daemon=True)
57
+ self.thread.start()
58
+
59
+ def stop(self) -> None:
60
+ """Stop the client."""
61
+ self.shutdown.set()
62
+ if self.thread:
63
+ self.thread.join()
64
+ self.thread = None
65
+
66
+ def finalize(self) -> None:
67
+ """Finalize the client."""
68
+ self.stop()
69
+ self.thread = None
fastled/types.py CHANGED
@@ -1,18 +1,11 @@
1
+ import argparse
1
2
  from dataclasses import dataclass
3
+ from enum import Enum
2
4
  from typing import Any
3
5
 
4
6
 
5
7
  @dataclass
6
- class CompiledResult:
7
- """Dataclass to hold the result of the compilation."""
8
-
9
- success: bool
10
- fastled_js: str
11
- hash_value: str | None
12
-
13
-
14
- @dataclass
15
- class WebCompileResult:
8
+ class CompileResult:
16
9
  success: bool
17
10
  stdout: str
18
11
  hash_value: str | None
@@ -23,3 +16,46 @@ class WebCompileResult:
23
16
 
24
17
  def to_dict(self) -> dict[str, Any]:
25
18
  return self.__dict__.copy()
19
+
20
+
21
+ class CompileServerError(Exception):
22
+ """Error class for failing to instantiate CompileServer."""
23
+
24
+ pass
25
+
26
+
27
+ class BuildMode(Enum):
28
+ DEBUG = "DEBUG"
29
+ QUICK = "QUICK"
30
+ RELEASE = "RELEASE"
31
+
32
+ @classmethod
33
+ def from_string(cls, mode_str: str) -> "BuildMode":
34
+ try:
35
+ return cls[mode_str.upper()]
36
+ except KeyError:
37
+ valid_modes = [mode.name for mode in cls]
38
+ raise ValueError(f"BUILD_MODE must be one of {valid_modes}, got {mode_str}")
39
+
40
+ @staticmethod
41
+ def from_args(args: argparse.Namespace) -> "BuildMode":
42
+ if args.debug:
43
+ return BuildMode.DEBUG
44
+ elif args.release:
45
+ return BuildMode.RELEASE
46
+ else:
47
+ return BuildMode.QUICK
48
+
49
+
50
+ class Platform(Enum):
51
+ WASM = "WASM"
52
+
53
+ @classmethod
54
+ def from_string(cls, platform_str: str) -> "Platform":
55
+ try:
56
+ return cls[platform_str.upper()]
57
+ except KeyError:
58
+ valid_modes = [mode.name for mode in cls]
59
+ raise ValueError(
60
+ f"Platform must be one of {valid_modes}, got {platform_str}"
61
+ )
fastled/web_compile.py CHANGED
@@ -11,10 +11,9 @@ from pathlib import Path
11
11
 
12
12
  import httpx
13
13
 
14
- from fastled.build_mode import BuildMode
15
14
  from fastled.settings import SERVER_PORT
16
15
  from fastled.sketch import get_sketch_files
17
- from fastled.types import WebCompileResult
16
+ from fastled.types import BuildMode, CompileResult
18
17
  from fastled.util import hash_file
19
18
 
20
19
  DEFAULT_HOST = "https://fastled.onrender.com"
@@ -160,7 +159,7 @@ def web_compile(
160
159
  auth_token: str | None = None,
161
160
  build_mode: BuildMode | None = None,
162
161
  profile: bool = False,
163
- ) -> WebCompileResult:
162
+ ) -> CompileResult:
164
163
  if isinstance(directory, str):
165
164
  directory = Path(directory)
166
165
  host = _sanitize_host(host or DEFAULT_HOST)
@@ -171,7 +170,7 @@ def web_compile(
171
170
  raise FileNotFoundError(f"Directory not found: {directory}")
172
171
  zip_result = zip_files(directory, build_mode=build_mode)
173
172
  if isinstance(zip_result, Exception):
174
- return WebCompileResult(
173
+ return CompileResult(
175
174
  success=False, stdout=str(zip_result), hash_value=None, zip_bytes=b""
176
175
  )
177
176
  zip_bytes = zip_result.zip_bytes
@@ -187,7 +186,7 @@ def web_compile(
187
186
  connection_result = find_good_connection(urls)
188
187
  if connection_result is None:
189
188
  print("Connection failed to all endpoints")
190
- return WebCompileResult(
189
+ return CompileResult(
191
190
  success=False,
192
191
  stdout="Connection failed",
193
192
  hash_value=None,
@@ -229,7 +228,7 @@ def web_compile(
229
228
  if response.status_code != 200:
230
229
  json_response = response.json()
231
230
  detail = json_response.get("detail", "Could not compile")
232
- return WebCompileResult(
231
+ return CompileResult(
233
232
  success=False, stdout=detail, hash_value=None, zip_bytes=b""
234
233
  )
235
234
 
@@ -270,7 +269,7 @@ def web_compile(
270
269
  relative_path = file_path.relative_to(extract_path)
271
270
  out_zip.write(file_path, relative_path)
272
271
 
273
- return WebCompileResult(
272
+ return CompileResult(
274
273
  success=True,
275
274
  stdout=stdout,
276
275
  hash_value=hash_value,
@@ -281,6 +280,6 @@ def web_compile(
281
280
  raise
282
281
  except httpx.HTTPError as e:
283
282
  print(f"Error: {e}")
284
- return WebCompileResult(
283
+ return CompileResult(
285
284
  success=False, stdout=str(e), hash_value=None, zip_bytes=b""
286
285
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastled
3
- Version: 1.1.37
3
+ Version: 1.1.39
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies
@@ -61,60 +61,155 @@ pip install fastled
61
61
 
62
62
  **Note that you may need to install x86 docker emulation on Mac-m1 and later, as this is an x86 only image at the prsent.**
63
63
 
64
- # Use
64
+ # Command Line Use
65
65
 
66
66
  Change to the directory where the sketch lives and run, will run the compilation
67
67
  on the web compiler.
68
68
 
69
69
  ```bash
70
+ # This will use the web-compiler, unless you have docker installed in which case a local
71
+ # server will be instantiated automatically.
70
72
  cd <SKETCH-DIRECTORY>
71
73
  fastled
72
74
  ```
73
75
 
74
- Or if you have docker you can run a server automatically.
76
+ Forces the local server to to spawn in order to run to do the compile.
75
77
 
76
78
  ```bash
77
79
  cd <SKETCH-DIRECTORY>
78
- fastled --local
80
+ fastled --local # if server doesn't already exist, one is created.
79
81
  ```
80
82
 
81
83
  You can also spawn a server in one process and then access it in another, like this:
82
84
 
83
85
  ```bash
84
- fastled --server
86
+ fastled --server # server will now run in the background.
85
87
  # now launch the client
86
- fastled examples/wasm --local
88
+ fastled examples/wasm --local # local will find the local server use it do the compile.
87
89
  ```
88
90
 
89
91
  After compilation a web browser windows will pop up. Changes to the sketch will automatically trigger a recompilation.
90
92
 
91
- # Hot reload by default
93
+ # Python Api
94
+
95
+ **Compiling through the api**
96
+ ```python
97
+
98
+ from fastapi import Api, CompileResult
99
+
100
+ out: CompileResult = Api.web_compile("path/to/sketch")
101
+ print(out.success)
102
+ print(out.stdout)
103
+
104
+ ```
105
+
106
+ **Launching a compile server**
107
+ ```python
108
+
109
+ from fastapi import Api, CompileServer
110
+
111
+ server: CompileServer = Api.spawn_server()
112
+ print(f"Local server running at {server.url()}")
113
+ server.web_compile("path/to/sketch") # output will be "path/to/sketch/fastled_js"
114
+ server.stop()
115
+ ```
116
+
117
+ **Launching a server in a scope**
118
+ ```python
119
+
120
+ from fastapi import Api
121
+
122
+ # Launching a server in a scope
123
+ with Api.server() as server:
124
+ server.web_compile("path/to/sketch")
125
+
126
+ ```
127
+
128
+ **Initializing a project example from the web compiler**
129
+ ```python
130
+
131
+ from fastapi import Api
132
+
133
+ examples = Api.get_examples()
134
+ print(f"Print available examples: {examples}")
135
+ Api.project_init(examples[0])
136
+
137
+
138
+ ```
139
+
140
+ **Initializing a project example from the CompileServer**
141
+ ```python
142
+
143
+ from fastapi import Api
144
+
145
+ with Api.server() as server:
146
+ examples = server.get_examples()
147
+ server.project_init(examples[0])
148
+
149
+ ```
150
+
151
+ **LiveClient will auto-trigger a build on code changes, just like the cli does**
152
+ ```python
153
+
154
+ # Live Client will compile against the web-compiler
155
+ from fastapi import Api, LiveClient
156
+ client: LiveClient = Api.live_client(
157
+ "path/to/sketch_directory",
158
+ )
159
+ # Now user can start editing their sketch and it will auto-compile
160
+ # ... after a while stop it like this.
161
+ client.stop()
162
+ ```
163
+
164
+ **LiveClient with local CompileServer**
165
+ ```python
166
+
167
+ # Live Client will compile against a local server.
168
+ from fastapi import Api, LiveClient
169
+
170
+ with Api.server() as server:
171
+ client: LiveClient = Api.live_client(
172
+ "path/to/sketch_directory",
173
+ host=server
174
+ )
175
+ # Now user can start editing their sketch and it will auto-compile
176
+ # ... after a while stop it like this.
177
+ client.stop()
178
+ ```
179
+
180
+ # Features
181
+
182
+ ## Hot reload by default
92
183
 
93
184
  Once launched, the compiler will remain open, listening to changes and recompiling as necessary and hot-reloading the sketch into the current browser.
94
185
 
95
186
  This style of development should be familiar to those doing web development.
96
187
 
97
- # Hot Reload for working with the FastLED repo
188
+ ## Hot Reload fastled/src when working in the FastLED repo
98
189
 
99
190
  If you launch `fastled` in the FastLED repo then this tool will automatically detect this and map the src directory into the
100
191
  host container. Whenever there are changes in the source code from the mapped directory, then these will be re-compiled
101
192
  on the next change or if you hit the space bar when prompted. Unlike a sketch folder, a re-compile on the FastLED src
102
193
  can be much longer, for example if you modify a header file.
103
194
 
104
- # Data
195
+ ## Big Data in `/data` directory won't be round-tripped
105
196
 
106
197
  Huge blobs of data like video will absolutely kill the compile performance as these blobs would normally have to be shuffled
107
198
  back and forth. Therefore a special directory `data/` is implicitly used to hold this blob data. Any data in this directory
108
199
  will be replaced with a stub containing the size and hash of the file during upload. On download these stubs are swapped back
109
- with their originals.
200
+ with their originals during decompression.
110
201
 
111
202
  The wasm compiler will recognize all files in the `data/` directory and generate a `files.json` manifest and can be used
112
203
  in your wasm sketch using an emulated SD card system mounted at `/data/` on the SD Card. In order to increase load speed, these
113
- files will be asynchroniously streamed into the running sketch instance during runtime. The only caveat here is that although these files will be available during the setup() phase of the sketch, they will not be fully hydrated, so if you do a seek(end) of these files the results are undefined.
204
+ files will be asynchroniously streamed into the running sketch instance during runtime. Files named with *.json, *.csv, *.txt will be
205
+ immediately injected in the app before setup() is called and can be used immediatly in setup() in their entirety.
206
+
207
+ All other files will be streamed in. The `Video` element in FastLED is designed to gracefully handle missing data streamed in through
208
+ the file system.
114
209
 
115
210
  For an example of how to use this see `examples/SdCard` which is fully wasm compatible.
116
211
 
117
- # Compile Speed
212
+ ## Compile Speed
118
213
 
119
214
  The compile speeds for this compiler have been optimized pretty much to the max. There are three compile settings available to the user. The default is `--quick`. Aggressive optimizations are done with `--release` which will aggressively optimize for size. The speed difference between `--release` and `--quick` seems negligable. But `--release` will produce a ~1/3 smaller binary. There is also `--debug`, which will include symbols necessary for debugging and getting the C++ function symbols working correctly in the browser during step through debugging. It works better than expected, but don't expect to have gdb or msvc debugger level of debugging experience.
120
215
 
@@ -122,24 +217,22 @@ We use `ccache` to cache object files. This seems actually help a lot and is bet
122
217
 
123
218
  The compilation to wasm will happen under a lock. Removing this lock requires removing the platformio toolchain as the compiler backend which enforces it's own internal lock preventing parallel use.
124
219
 
125
- Simple syntax errors will be caught by the pre-processing step. This happens without a lock to reduce the single lock bottleneck.
126
-
127
- # Sketch Cache
220
+ ## Sketch Cache
128
221
 
129
- Sketchs are aggresively finger-printed and stored in a cache. White space, comments, and other superficial data will be stripped out during pre-processing and minimization for fingerprinting. This source file decimation is only used for finger
222
+ Sketchs are aggressively finger-printed and stored in a cache. White space, comments, and other superficial data will be stripped out during pre-processing and minimization for fingerprinting. This source file decimation is only used for finger
130
223
  printing while the actual source files are sent to compiler to preserve line numbers and file names.
131
224
 
132
225
  This pre-processing done is done via gcc and special regex's and will happen without a lock. This will allow you to have extremely quick recompiles for whitespace and changes in comments even if the compiler is executing under it's lock.
133
226
 
134
- # Local compiles
227
+ ## Local compiles
135
228
 
136
229
  If the web-compiler get's congested then it's recommend that you run the compiler locally. This requires docker and will be invoked whenever you pass in `--local`. This will first pull the most recent Docker image of the Fastled compiler, launching a webserver and then connecting to it with the client once it's been up.
137
230
 
138
- # Auto updates
231
+ ## Auto updates
139
232
 
140
233
  In server mode the git repository will be cloned as a side repo and then periodically updated and rsync'd to the src directory. This allows a long running instance to stay updated.
141
234
 
142
- ### Wasm compatibility with Arduino sketchs
235
+ ## Compatibility with Arduino sketchs
143
236
 
144
237
  The compatibility is actually pretty good. Most simple sketchs should compile out of the box. Even some of the avr platform includes are stubbed out to make it work. The familiar `digitalWrite()`, `Serial.println()` and other common functions work. Although `digitalRead()` will always return 0 and `analogRead()` will return random numbers.
145
238
 
@@ -163,6 +256,8 @@ A: A big chunk of space is being used by unnecessary javascript `emscripten` is
163
256
 
164
257
  # Revisions
165
258
 
259
+ * 1.1.39 - Added `LiveClient`, `fastled.Api.live_server()` will spawn it. Allows user to have a live compiling client that re-triggers a compile on file changes.
260
+ * 1.1.38 - Cleanup the `fastled.Api` object and streamline for general use.
166
261
  * 1.1.37 - `Test.test_examples()` is now unit tested to work correctly.
167
262
  * 1.1.36 - We now have an api. `from fastled import Api` and `from fastled import Test` for testing.
168
263
  * 1.1.35 - When searching for files cap the limit at a high amount to prevent hang.
@@ -1,13 +1,13 @@
1
- fastled/__init__.py,sha256=PWN2ftSaLElmfJkYyv0gmGKZgBTljVv8D58Di0JGmeY,2539
1
+ fastled/__init__.py,sha256=SQF8-PpioJm6DAQn5vCcc-muwXHBfs0loIofSRoWK8g,3613
2
2
  fastled/app.py,sha256=3xg7oVD-UYnKPU8SAY-Cs5UnAYdwpdpuEFRR2N8P1Tg,1787
3
- fastled/build_mode.py,sha256=joMwsV4K1y_LijT4gEAcjx69RZBoe_KmFmHZdPYbL_4,631
4
3
  fastled/cli.py,sha256=CNR_pQR0sNVPNuv8e_nmm-0PI8sU-eUBUgnWgWkzW9c,237
5
- fastled/client_server.py,sha256=rre250O1uCx6JFi_nn6fhKUncgKOw3F7BgN9O9wbCEM,11440
6
- fastled/compile_server.py,sha256=QxkXLpj9q7BWhPbS2NPeFF4WRne-358o-8fg4VEDH1o,2427
7
- fastled/compile_server_impl.py,sha256=cB5WZ9znNhCFe2HKl-p1ZonfYKeaJOUyV3uHfkQtTZk,8481
4
+ fastled/client_server.py,sha256=KQsBKjHfkhgF5EOCHSbJcqu0dPKlOCsbXA2V035VCH0,12403
5
+ fastled/compile_server.py,sha256=Z7rHFs3M6QPbSCsbgHAQDk6GTVAJMMPCXtD4Y0mu8RM,2659
6
+ fastled/compile_server_impl.py,sha256=ClBLtFHB0ucaT8tAJfI6o3bJ-LRnXc4Pxy7bVKnFiww,8803
8
7
  fastled/docker_manager.py,sha256=zBCFGk2P3_bS7_SUQ5j2lpsOS3RvIzXYkrJXC6xP69k,25383
9
8
  fastled/filewatcher.py,sha256=LwEQJkqADsArZyY499RLAer6JjJyDwaQBcAvT7xmp3c,6708
10
9
  fastled/keyboard.py,sha256=Zz_ggxOUTX2XQEy6K6kAoorVlUev4wEk9Awpvv9aStA,3241
10
+ fastled/live_client.py,sha256=_KvqmyUyyGpoYET1Z9CdeUVoIbFjIUWwPcTp5XCQuxY,2075
11
11
  fastled/open_browser.py,sha256=vzMBcpDNY0f-Bx9KmEILKDANZ6gvsywCVwn1FRhPXh4,1770
12
12
  fastled/parse_args.py,sha256=37WsELphNEqGQgmjppZx6uMWE2E-dZ58zCKUl-3mr3Q,6150
13
13
  fastled/paths.py,sha256=VsPmgu0lNSCFOoEC0BsTYzDygXqy15AHUfN-tTuzDZA,99
@@ -17,14 +17,14 @@ fastled/settings.py,sha256=3eMKv0tLXgIQ0CFDboIp_l5_71rzIIyWg353YjnYJnc,323
17
17
  fastled/sketch.py,sha256=483TrrIdZJfo1MIu5FkD-V5OGmOfHmsZ2f6VvNsJBJM,3299
18
18
  fastled/spinner.py,sha256=VHxmvB92P0Z_zYxRajb5HiNmkHHvZ5dG7hKtZltzpcs,867
19
19
  fastled/string_diff.py,sha256=UR1oRhg9lsPzAG4bn_MwJMCn0evP5AigkBiwLiI9fgA,1354
20
- fastled/types.py,sha256=4uSSuOUhZptTreor6CXXGSk-FIMOdwHfdvFI-4Yx6AY,475
20
+ fastled/types.py,sha256=PpSEtzFCkWtSIEMC0QXGl966R97vLoryVl3yFW0YhTs,1475
21
21
  fastled/util.py,sha256=t4M3NFMhnCzfYbLvIyJi0RdFssZqbTN_vVIaej1WV-U,265
22
- fastled/web_compile.py,sha256=0jXFeej-YhOxcjIau8Ru8qEu-ULlsFjaufbqKYUUXjQ,10312
22
+ fastled/web_compile.py,sha256=05PeLJ77QQC6PUKjDhsntBmyBola6QQIfF2k-zjYNE4,10261
23
23
  fastled/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
24
24
  fastled/test/examples.py,sha256=EDXb6KastKOOWzew99zrpmcNcXTcAtYi8eud6F1pnWA,980
25
- fastled-1.1.37.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
26
- fastled-1.1.37.dist-info/METADATA,sha256=2_Eusq7JNju_ZOcwRSWmACJv6vY69A48T2hFs3kbSLE,15329
27
- fastled-1.1.37.dist-info/WHEEL,sha256=0VNUDWQJzfRahYI3neAhz2UVbRCtztpN5dPHAGvmGXc,109
28
- fastled-1.1.37.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
29
- fastled-1.1.37.dist-info/top_level.txt,sha256=Bbv5kpJpZhWNCvDF4K0VcvtBSDMa8B7PTOrZa9CezHY,8
30
- fastled-1.1.37.dist-info/RECORD,,
25
+ fastled-1.1.39.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
26
+ fastled-1.1.39.dist-info/METADATA,sha256=a-iU6xg5tbGNPN-2l0Bydnk3Xax9-jfWAFV7ltioC6U,17889
27
+ fastled-1.1.39.dist-info/WHEEL,sha256=0VNUDWQJzfRahYI3neAhz2UVbRCtztpN5dPHAGvmGXc,109
28
+ fastled-1.1.39.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
29
+ fastled-1.1.39.dist-info/top_level.txt,sha256=Bbv5kpJpZhWNCvDF4K0VcvtBSDMa8B7PTOrZa9CezHY,8
30
+ fastled-1.1.39.dist-info/RECORD,,
fastled/build_mode.py DELETED
@@ -1,25 +0,0 @@
1
- import argparse
2
- from enum import Enum
3
-
4
-
5
- class BuildMode(Enum):
6
- DEBUG = "DEBUG"
7
- QUICK = "QUICK"
8
- RELEASE = "RELEASE"
9
-
10
- @classmethod
11
- def from_string(cls, mode_str: str) -> "BuildMode":
12
- try:
13
- return cls[mode_str.upper()]
14
- except KeyError:
15
- valid_modes = [mode.name for mode in cls]
16
- raise ValueError(f"BUILD_MODE must be one of {valid_modes}, got {mode_str}")
17
-
18
-
19
- def get_build_mode(args: argparse.Namespace) -> BuildMode:
20
- if args.debug:
21
- return BuildMode.DEBUG
22
- elif args.release:
23
- return BuildMode.RELEASE
24
- else:
25
- return BuildMode.QUICK