fastled 1.2.70__py3-none-any.whl → 1.2.73__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
@@ -13,7 +13,7 @@ from .types import BuildMode, CompileResult, CompileServerError
13
13
  # IMPORTANT! There's a bug in github which will REJECT any version update
14
14
  # that has any other change in the repo. Please bump the version as the
15
15
  # ONLY change in a commit, or else the pypi update and the release will fail.
16
- __version__ = "1.2.70"
16
+ __version__ = "1.2.73"
17
17
 
18
18
  DOCKER_FILE = (
19
19
  "https://raw.githubusercontent.com/zackees/fastled-wasm/refs/heads/main/Dockerfile"
fastled/app.py CHANGED
@@ -45,7 +45,6 @@ def run_server(args: Args) -> int:
45
45
  def main() -> int:
46
46
  from fastled import __version__
47
47
 
48
- print(f"FastLED version: {__version__}")
49
48
  args = parse_args()
50
49
  interactive: bool = args.interactive
51
50
  has_server = args.server
@@ -55,6 +54,9 @@ def main() -> int:
55
54
  # directory: Path | None = Path(args.directory).absolute() if args.directory else None
56
55
  directory: Path | None = Path(args.directory) if args.directory else None
57
56
 
57
+ # now it is safe to print out the version
58
+ print(f"FastLED version: {__version__}")
59
+
58
60
  if update:
59
61
  # Force auto_update to ensure update check happens
60
62
  compile_server = CompileServer(interactive=False, auto_updates=True)
@@ -1,295 +1,295 @@
1
- import subprocess
2
- import sys
3
- import time
4
- import traceback
5
- import warnings
6
- from datetime import datetime, timezone
7
- from pathlib import Path
8
-
9
- import httpx
10
-
11
- from fastled.docker_manager import (
12
- DISK_CACHE,
13
- Container,
14
- DockerManager,
15
- RunningContainer,
16
- Volume,
17
- )
18
- from fastled.interactive_srcs import INTERACTIVE_SOURCES
19
- from fastled.settings import DEFAULT_CONTAINER_NAME, IMAGE_NAME, SERVER_PORT
20
- from fastled.sketch import looks_like_fastled_repo
21
- from fastled.types import BuildMode, CompileResult, CompileServerError
22
-
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
38
-
39
-
40
- class CompileServerImpl:
41
- def __init__(
42
- self,
43
- interactive: bool = False,
44
- auto_updates: bool | None = None,
45
- mapped_dir: Path | None = None,
46
- auto_start: bool = True,
47
- container_name: str | None = None,
48
- remove_previous: bool = False,
49
- ) -> None:
50
- container_name = container_name or DEFAULT_CONTAINER_NAME
51
- if interactive and not mapped_dir:
52
- raise ValueError(
53
- "Interactive mode requires a mapped directory point to a sketch"
54
- )
55
- if not interactive and mapped_dir:
56
- warnings.warn(
57
- f"Mapped directory {mapped_dir} is ignored in non-interactive mode"
58
- )
59
- self.container_name = container_name
60
- self.mapped_dir = mapped_dir
61
- self.docker = DockerManager()
62
- self.fastled_src_dir: Path | None = _try_get_fastled_src(Path(".").resolve())
63
- self.interactive = interactive
64
- self.running_container: RunningContainer | None = None
65
- self.auto_updates = auto_updates
66
- self.remove_previous = remove_previous
67
- self._port = 0 # 0 until compile server is started
68
- if auto_start:
69
- self.start()
70
-
71
- def start(self, wait_for_startup=True) -> None:
72
- if not DockerManager.is_docker_installed():
73
- raise CompileServerError("Docker is not installed")
74
- if self._port != 0:
75
- warnings.warn("Server has already been started")
76
- self._port = self._start()
77
- if wait_for_startup:
78
- ok = self.wait_for_startup()
79
- if not ok:
80
- if self.interactive:
81
- print("Exited from container.")
82
- sys.exit(0)
83
- return
84
- raise CompileServerError("Server did not start")
85
- if not self.interactive:
86
- msg = f"# FastLED Compile Server started at {self.url()} #"
87
- print("\n" + "#" * len(msg))
88
- print(msg)
89
- print("#" * len(msg) + "\n")
90
-
91
- def web_compile(
92
- self,
93
- directory: Path | str,
94
- build_mode: BuildMode = BuildMode.QUICK,
95
- profile: bool = False,
96
- ) -> CompileResult:
97
- from fastled.web_compile import web_compile # avoid circular import
98
-
99
- if not self._port:
100
- raise RuntimeError("Server has not been started yet")
101
- if not self.ping():
102
- raise RuntimeError("Server is not running")
103
- out: CompileResult = web_compile(
104
- directory, host=self.url(), build_mode=build_mode, profile=profile
105
- )
106
- return out
107
-
108
- def project_init(
109
- self, example: str | None = None, outputdir: Path | None = None
110
- ) -> None:
111
- from fastled.project_init import project_init # avoid circular import
112
-
113
- project_init(example=example, outputdir=outputdir)
114
-
115
- @property
116
- def running(self) -> tuple[bool, Exception | None]:
117
- if not self._port:
118
- return False, Exception("Docker hasn't been initialzed with a port yet")
119
- if not DockerManager.is_docker_installed():
120
- return False, Exception("Docker is not installed")
121
- docker_running, err = self.docker.is_running()
122
- if not docker_running:
123
- IS_MAC = sys.platform == "darwin"
124
- if IS_MAC:
125
- if "FileNotFoundError" in str(err):
126
- traceback.print_exc()
127
- print("\n\nNone fatal debug print for MacOS\n")
128
- return False, err
129
- ok: bool = self.docker.is_container_running(self.container_name)
130
- if ok:
131
- return True, None
132
- else:
133
- return False, Exception("Docker is not running")
134
-
135
- def using_fastled_src_dir_volume(self) -> bool:
136
- out = self.fastled_src_dir is not None
137
- if out:
138
- print(f"Using FastLED source directory: {self.fastled_src_dir}")
139
- return out
140
-
141
- def port(self) -> int:
142
- if self._port == 0:
143
- warnings.warn("Server has not been started yet")
144
- return self._port
145
-
146
- def url(self) -> str:
147
- if self._port == 0:
148
- warnings.warn("Server has not been started yet")
149
- return f"http://localhost:{self._port}"
150
-
151
- def ping(self) -> bool:
152
- try:
153
- response = httpx.get(
154
- f"http://localhost:{self._port}", follow_redirects=True
155
- )
156
- if response.status_code < 400:
157
- return True
158
- except KeyboardInterrupt:
159
- raise
160
- except Exception:
161
- pass
162
- return False
163
-
164
- # by default this is automatically called by the constructor, unless
165
- # auto_start is set to False.
166
- def wait_for_startup(self, timeout: int = 100) -> bool:
167
- """Wait for the server to start up."""
168
- start_time = time.time()
169
- while time.time() - start_time < timeout:
170
- # ping the server to see if it's up
171
- if not self._port:
172
- return False
173
- # use httpx to ping the server
174
- # if successful, return True
175
- if self.ping():
176
- return True
177
- time.sleep(0.1)
178
- if not self.docker.is_container_running(self.container_name):
179
- return False
180
- return False
181
-
182
- def _start(self) -> int:
183
- print("Compiling server starting")
184
-
185
- # Ensure Docker is running
186
- if not self.docker.is_running():
187
- if not self.docker.start():
188
- print("Docker could not be started. Exiting.")
189
- raise RuntimeError("Docker could not be started. Exiting.")
190
- now = datetime.now(timezone.utc)
191
- now_str = now.strftime("%Y-%m-%d")
192
-
193
- upgrade = False
194
- if self.auto_updates is None:
195
- prev_date_str = DISK_CACHE.get("last-update")
196
- if prev_date_str != now_str:
197
- print("One day has passed, checking docker for updates")
198
- upgrade = True
199
- else:
200
- upgrade = self.auto_updates
201
- updated = self.docker.validate_or_download_image(
202
- image_name=IMAGE_NAME, tag="latest", upgrade=upgrade
203
- )
204
- DISK_CACHE.put("last-update", now_str)
205
-
206
- print("Docker image now validated")
207
- port = SERVER_PORT
208
- if self.interactive:
209
- server_command = ["/bin/bash"]
210
- else:
211
- server_command = ["python", "/js/run.py", "server"] + SERVER_OPTIONS
212
- ports = {80: port}
213
- volumes = []
214
- if self.fastled_src_dir:
215
- print(
216
- f"Mounting FastLED source directory {self.fastled_src_dir} into container /host/fastled/src"
217
- )
218
- volumes.append(
219
- Volume(
220
- host_path=str(self.fastled_src_dir),
221
- container_path="/host/fastled/src",
222
- mode="ro",
223
- )
224
- )
225
- if self.interactive:
226
- # add the mapped directory to the container
227
- print(f"Mounting {self.mapped_dir} into container /mapped")
228
- assert self.mapped_dir is not None
229
- dir_name = self.mapped_dir.name
230
- if not volumes:
231
- volumes = []
232
- volumes.append(
233
- Volume(
234
- host_path=str(self.mapped_dir),
235
- container_path=f"/mapped/{dir_name}",
236
- mode="rw",
237
- )
238
- )
239
- if self.fastled_src_dir is not None:
240
- # to allow for interactive compilation
241
- interactive_sources = list(INTERACTIVE_SOURCES)
242
- for src in interactive_sources:
243
- src_path = Path(src).absolute()
244
- if src_path.exists():
245
- print(f"Mounting {src} into container")
246
- volumes.append(
247
- Volume(
248
- host_path=str(src_path),
249
- container_path=f"/js/fastled/{src}",
250
- mode="rw",
251
- )
252
- )
253
- else:
254
- print(f"Could not find {src}")
255
-
256
- cmd_str = subprocess.list2cmdline(server_command)
257
- if not self.interactive:
258
- container: Container = self.docker.run_container_detached(
259
- image_name=IMAGE_NAME,
260
- tag="latest",
261
- container_name=self.container_name,
262
- command=cmd_str,
263
- ports=ports,
264
- volumes=volumes,
265
- remove_previous=self.interactive or self.remove_previous or updated,
266
- )
267
- self.running_container = self.docker.attach_and_run(container)
268
- assert self.running_container is not None, "Container should be running"
269
- print("Compile server starting")
270
- return port
271
- else:
272
- self.docker.run_container_interactive(
273
- image_name=IMAGE_NAME,
274
- tag="latest",
275
- container_name=self.container_name,
276
- command=cmd_str,
277
- ports=ports,
278
- volumes=volumes,
279
- )
280
-
281
- print("Exiting interactive mode")
282
- return port
283
-
284
- def process_running(self) -> bool:
285
- return self.docker.is_container_running(self.container_name)
286
-
287
- def stop(self) -> None:
288
- if self.docker.is_suspended:
289
- return
290
- if self.running_container:
291
- self.running_container.detach()
292
- self.running_container = None
293
- self.docker.suspend_container(self.container_name)
294
- self._port = 0
295
- print("Compile server stopped")
1
+ import subprocess
2
+ import sys
3
+ import time
4
+ import traceback
5
+ import warnings
6
+ from datetime import datetime, timezone
7
+ from pathlib import Path
8
+
9
+ import httpx
10
+
11
+ from fastled.docker_manager import (
12
+ DISK_CACHE,
13
+ Container,
14
+ DockerManager,
15
+ RunningContainer,
16
+ Volume,
17
+ )
18
+ from fastled.interactive_srcs import INTERACTIVE_SOURCES
19
+ from fastled.settings import DEFAULT_CONTAINER_NAME, IMAGE_NAME, SERVER_PORT
20
+ from fastled.sketch import looks_like_fastled_repo
21
+ from fastled.types import BuildMode, CompileResult, CompileServerError
22
+
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
38
+
39
+
40
+ class CompileServerImpl:
41
+ def __init__(
42
+ self,
43
+ interactive: bool = False,
44
+ auto_updates: bool | None = None,
45
+ mapped_dir: Path | None = None,
46
+ auto_start: bool = True,
47
+ container_name: str | None = None,
48
+ remove_previous: bool = False,
49
+ ) -> None:
50
+ container_name = container_name or DEFAULT_CONTAINER_NAME
51
+ if interactive and not mapped_dir:
52
+ raise ValueError(
53
+ "Interactive mode requires a mapped directory point to a sketch"
54
+ )
55
+ if not interactive and mapped_dir:
56
+ warnings.warn(
57
+ f"Mapped directory {mapped_dir} is ignored in non-interactive mode"
58
+ )
59
+ self.container_name = container_name
60
+ self.mapped_dir = mapped_dir
61
+ self.docker = DockerManager()
62
+ self.fastled_src_dir: Path | None = _try_get_fastled_src(Path(".").resolve())
63
+ self.interactive = interactive
64
+ self.running_container: RunningContainer | None = None
65
+ self.auto_updates = auto_updates
66
+ self.remove_previous = remove_previous
67
+ self._port = 0 # 0 until compile server is started
68
+ if auto_start:
69
+ self.start()
70
+
71
+ def start(self, wait_for_startup=True) -> None:
72
+ if not DockerManager.is_docker_installed():
73
+ raise CompileServerError("Docker is not installed")
74
+ if self._port != 0:
75
+ warnings.warn("Server has already been started")
76
+ self._port = self._start()
77
+ if wait_for_startup:
78
+ ok = self.wait_for_startup()
79
+ if not ok:
80
+ if self.interactive:
81
+ print("Exited from container.")
82
+ sys.exit(0)
83
+ return
84
+ raise CompileServerError("Server did not start")
85
+ if not self.interactive:
86
+ msg = f"# FastLED Compile Server started at {self.url()} #"
87
+ print("\n" + "#" * len(msg))
88
+ print(msg)
89
+ print("#" * len(msg) + "\n")
90
+
91
+ def web_compile(
92
+ self,
93
+ directory: Path | str,
94
+ build_mode: BuildMode = BuildMode.QUICK,
95
+ profile: bool = False,
96
+ ) -> CompileResult:
97
+ from fastled.web_compile import web_compile # avoid circular import
98
+
99
+ if not self._port:
100
+ raise RuntimeError("Server has not been started yet")
101
+ if not self.ping():
102
+ raise RuntimeError("Server is not running")
103
+ out: CompileResult = web_compile(
104
+ directory, host=self.url(), build_mode=build_mode, profile=profile
105
+ )
106
+ return out
107
+
108
+ def project_init(
109
+ self, example: str | None = None, outputdir: Path | None = None
110
+ ) -> None:
111
+ from fastled.project_init import project_init # avoid circular import
112
+
113
+ project_init(example=example, outputdir=outputdir)
114
+
115
+ @property
116
+ def running(self) -> tuple[bool, Exception | None]:
117
+ if not self._port:
118
+ return False, Exception("Docker hasn't been initialzed with a port yet")
119
+ if not DockerManager.is_docker_installed():
120
+ return False, Exception("Docker is not installed")
121
+ docker_running, err = self.docker.is_running()
122
+ if not docker_running:
123
+ IS_MAC = sys.platform == "darwin"
124
+ if IS_MAC:
125
+ if "FileNotFoundError" in str(err):
126
+ traceback.print_exc()
127
+ print("\n\nNone fatal debug print for MacOS\n")
128
+ return False, err
129
+ ok: bool = self.docker.is_container_running(self.container_name)
130
+ if ok:
131
+ return True, None
132
+ else:
133
+ return False, Exception("Docker is not running")
134
+
135
+ def using_fastled_src_dir_volume(self) -> bool:
136
+ out = self.fastled_src_dir is not None
137
+ if out:
138
+ print(f"Using FastLED source directory: {self.fastled_src_dir}")
139
+ return out
140
+
141
+ def port(self) -> int:
142
+ if self._port == 0:
143
+ warnings.warn("Server has not been started yet")
144
+ return self._port
145
+
146
+ def url(self) -> str:
147
+ if self._port == 0:
148
+ warnings.warn("Server has not been started yet")
149
+ return f"http://localhost:{self._port}"
150
+
151
+ def ping(self) -> bool:
152
+ try:
153
+ response = httpx.get(
154
+ f"http://localhost:{self._port}", follow_redirects=True
155
+ )
156
+ if response.status_code < 400:
157
+ return True
158
+ except KeyboardInterrupt:
159
+ raise
160
+ except Exception:
161
+ pass
162
+ return False
163
+
164
+ # by default this is automatically called by the constructor, unless
165
+ # auto_start is set to False.
166
+ def wait_for_startup(self, timeout: int = 100) -> bool:
167
+ """Wait for the server to start up."""
168
+ start_time = time.time()
169
+ while time.time() - start_time < timeout:
170
+ # ping the server to see if it's up
171
+ if not self._port:
172
+ return False
173
+ # use httpx to ping the server
174
+ # if successful, return True
175
+ if self.ping():
176
+ return True
177
+ time.sleep(0.1)
178
+ if not self.docker.is_container_running(self.container_name):
179
+ return False
180
+ return False
181
+
182
+ def _start(self) -> int:
183
+ print("Compiling server starting")
184
+
185
+ # Ensure Docker is running
186
+ if not self.docker.is_running():
187
+ if not self.docker.start():
188
+ print("Docker could not be started. Exiting.")
189
+ raise RuntimeError("Docker could not be started. Exiting.")
190
+ now = datetime.now(timezone.utc)
191
+ now_str = now.strftime("%Y-%m-%d")
192
+
193
+ upgrade = False
194
+ if self.auto_updates is None:
195
+ prev_date_str = DISK_CACHE.get("last-update")
196
+ if prev_date_str != now_str:
197
+ print("One day has passed, checking docker for updates")
198
+ upgrade = True
199
+ else:
200
+ upgrade = self.auto_updates
201
+ updated = self.docker.validate_or_download_image(
202
+ image_name=IMAGE_NAME, tag="latest", upgrade=upgrade
203
+ )
204
+ DISK_CACHE.put("last-update", now_str)
205
+
206
+ print("Docker image now validated")
207
+ port = SERVER_PORT
208
+ if self.interactive:
209
+ server_command = ["/bin/bash"]
210
+ else:
211
+ server_command = ["python", "/js/run.py", "server"] + SERVER_OPTIONS
212
+ ports = {80: port}
213
+ volumes = []
214
+ if self.fastled_src_dir:
215
+ print(
216
+ f"Mounting FastLED source directory {self.fastled_src_dir} into container /host/fastled/src"
217
+ )
218
+ volumes.append(
219
+ Volume(
220
+ host_path=str(self.fastled_src_dir),
221
+ container_path="/host/fastled/src",
222
+ mode="ro",
223
+ )
224
+ )
225
+ if self.interactive:
226
+ # add the mapped directory to the container
227
+ print(f"Mounting {self.mapped_dir} into container /mapped")
228
+ assert self.mapped_dir is not None
229
+ dir_name = self.mapped_dir.name
230
+ if not volumes:
231
+ volumes = []
232
+ volumes.append(
233
+ Volume(
234
+ host_path=str(self.mapped_dir),
235
+ container_path=f"/mapped/{dir_name}",
236
+ mode="rw",
237
+ )
238
+ )
239
+ if self.fastled_src_dir is not None:
240
+ # to allow for interactive compilation
241
+ interactive_sources = list(INTERACTIVE_SOURCES)
242
+ for src in interactive_sources:
243
+ src_path = Path(src).absolute()
244
+ if src_path.exists():
245
+ print(f"Mounting {src} into container")
246
+ volumes.append(
247
+ Volume(
248
+ host_path=str(src_path),
249
+ container_path=f"/js/fastled/{src}",
250
+ mode="rw",
251
+ )
252
+ )
253
+ else:
254
+ print(f"Could not find {src}")
255
+
256
+ cmd_str = subprocess.list2cmdline(server_command)
257
+ if not self.interactive:
258
+ container: Container = self.docker.run_container_detached(
259
+ image_name=IMAGE_NAME,
260
+ tag="latest",
261
+ container_name=self.container_name,
262
+ command=cmd_str,
263
+ ports=ports,
264
+ volumes=volumes,
265
+ remove_previous=self.interactive or self.remove_previous or updated,
266
+ )
267
+ self.running_container = self.docker.attach_and_run(container)
268
+ assert self.running_container is not None, "Container should be running"
269
+ print("Compile server starting")
270
+ return port
271
+ else:
272
+ self.docker.run_container_interactive(
273
+ image_name=IMAGE_NAME,
274
+ tag="latest",
275
+ container_name=self.container_name,
276
+ command=cmd_str,
277
+ ports=ports,
278
+ volumes=volumes,
279
+ )
280
+
281
+ print("Exiting interactive mode")
282
+ return port
283
+
284
+ def process_running(self) -> bool:
285
+ return self.docker.is_container_running(self.container_name)
286
+
287
+ def stop(self) -> None:
288
+ if self.docker.is_suspended:
289
+ return
290
+ if self.running_container:
291
+ self.running_container.detach()
292
+ self.running_container = None
293
+ self.docker.suspend_container(self.container_name)
294
+ self._port = 0
295
+ print("Compile server stopped")