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