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