fastled 1.1.45__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,251 @@
1
+ import subprocess
2
+ import time
3
+ import warnings
4
+ from datetime import datetime, timezone
5
+ from pathlib import Path
6
+
7
+ import httpx
8
+
9
+ from fastled.docker_manager import (
10
+ DISK_CACHE,
11
+ Container,
12
+ DockerManager,
13
+ RunningContainer,
14
+ )
15
+ from fastled.settings import SERVER_PORT
16
+ from fastled.sketch import looks_like_fastled_repo
17
+ from fastled.types import BuildMode, CompileResult, CompileServerError
18
+
19
+ _IMAGE_NAME = "niteris/fastled-wasm"
20
+ _DEFAULT_CONTAINER_NAME = "fastled-wasm-compiler"
21
+
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
+ ) -> 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
+ raise ValueError("Mapped directory is only used in interactive mode")
56
+ self.container_name = container_name
57
+ self.mapped_dir = mapped_dir
58
+ self.docker = DockerManager()
59
+ self.fastled_src_dir: Path | None = _try_get_fastled_src(Path(".").resolve())
60
+ self.interactive = interactive
61
+ self.running_container: RunningContainer | None = None
62
+ self.auto_updates = auto_updates
63
+ self._port = 0 # 0 until compile server is started
64
+ if auto_start:
65
+ self.start()
66
+
67
+ def start(self, wait_for_startup=True) -> None:
68
+ if not DockerManager.is_docker_installed():
69
+ raise CompileServerError("Docker is not installed")
70
+ if self._port != 0:
71
+ warnings.warn("Server has already been started")
72
+ self._port = self._start()
73
+ if 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")
82
+
83
+ def web_compile(
84
+ self,
85
+ directory: Path | str,
86
+ build_mode: BuildMode = BuildMode.QUICK,
87
+ profile: bool = False,
88
+ ) -> CompileResult:
89
+ from fastled.web_compile import web_compile # avoid circular import
90
+
91
+ if not self._port:
92
+ raise RuntimeError("Server has not been started yet")
93
+ if not self.ping():
94
+ raise RuntimeError("Server is not running")
95
+ out: CompileResult = web_compile(
96
+ directory, host=self.url(), build_mode=build_mode, profile=profile
97
+ )
98
+ return out
99
+
100
+ def project_init(
101
+ self, example: str | None = None, outputdir: Path | None = None
102
+ ) -> None:
103
+ from fastled.project_init import project_init # avoid circular import
104
+
105
+ project_init(example=example, outputdir=outputdir)
106
+
107
+ @property
108
+ def running(self) -> bool:
109
+ if not self._port:
110
+ return False
111
+ if not DockerManager.is_docker_installed():
112
+ return False
113
+ if not DockerManager.is_running():
114
+ return False
115
+ return self.docker.is_container_running(self.container_name)
116
+
117
+ def using_fastled_src_dir_volume(self) -> bool:
118
+ return self.fastled_src_dir is not None
119
+
120
+ def port(self) -> int:
121
+ if self._port == 0:
122
+ warnings.warn("Server has not been started yet")
123
+ return self._port
124
+
125
+ def url(self) -> str:
126
+ if self._port == 0:
127
+ warnings.warn("Server has not been started yet")
128
+ return f"http://localhost:{self._port}"
129
+
130
+ def ping(self) -> bool:
131
+ try:
132
+ response = httpx.get(
133
+ f"http://localhost:{self._port}", follow_redirects=True
134
+ )
135
+ if response.status_code < 400:
136
+ return True
137
+ except KeyboardInterrupt:
138
+ raise
139
+ except Exception:
140
+ pass
141
+ return False
142
+
143
+ # by default this is automatically called by the constructor, unless
144
+ # auto_start is set to False.
145
+ def wait_for_startup(self, timeout: int = 100) -> bool:
146
+ """Wait for the server to start up."""
147
+ start_time = time.time()
148
+ while time.time() - start_time < timeout:
149
+ # ping the server to see if it's up
150
+ if not self._port:
151
+ return False
152
+ # use httpx to ping the server
153
+ # if successful, return True
154
+ if self.ping():
155
+ return True
156
+ time.sleep(0.1)
157
+ if not self.docker.is_container_running(self.container_name):
158
+ return False
159
+ return False
160
+
161
+ def _start(self) -> int:
162
+ print("Compiling server starting")
163
+
164
+ # Ensure Docker is running
165
+ if not self.docker.is_running():
166
+ if not self.docker.start():
167
+ print("Docker could not be started. Exiting.")
168
+ raise RuntimeError("Docker could not be started. Exiting.")
169
+ now = datetime.now(timezone.utc)
170
+ now_str = now.strftime("%Y-%m-%d")
171
+
172
+ upgrade = False
173
+ if self.auto_updates is None:
174
+ prev_date_str = DISK_CACHE.get("last-update")
175
+ if prev_date_str != now_str:
176
+ print("One day has passed, checking docker for updates")
177
+ upgrade = True
178
+ else:
179
+ upgrade = self.auto_updates
180
+ self.docker.validate_or_download_image(
181
+ image_name=_IMAGE_NAME, tag="main", upgrade=upgrade
182
+ )
183
+ DISK_CACHE.put("last-update", now_str)
184
+
185
+ print("Docker image now validated")
186
+ port = SERVER_PORT
187
+ if self.interactive:
188
+ server_command = ["/bin/bash"]
189
+ else:
190
+ server_command = ["python", "/js/run.py", "server"] + SERVER_OPTIONS
191
+ ports = {80: port}
192
+ volumes = None
193
+ if self.fastled_src_dir:
194
+ print(
195
+ f"Mounting FastLED source directory {self.fastled_src_dir} into container /host/fastled/src"
196
+ )
197
+ volumes = {
198
+ str(self.fastled_src_dir): {"bind": "/host/fastled/src", "mode": "ro"}
199
+ }
200
+ if self.interactive:
201
+ # add the mapped directory to the container
202
+ print(f"Mounting {self.mapped_dir} into container /mapped")
203
+ # volumes = {str(self.mapped_dir): {"bind": "/mapped", "mode": "rw"}}
204
+ # add it
205
+ assert self.mapped_dir is not None
206
+ dir_name = self.mapped_dir.name
207
+ if not volumes:
208
+ volumes = {}
209
+ volumes[str(self.mapped_dir)] = {
210
+ "bind": f"/mapped/{dir_name}",
211
+ "mode": "rw",
212
+ }
213
+
214
+ cmd_str = subprocess.list2cmdline(server_command)
215
+ if not self.interactive:
216
+ container: Container = self.docker.run_container_detached(
217
+ image_name=_IMAGE_NAME,
218
+ tag="main",
219
+ container_name=self.container_name,
220
+ command=cmd_str,
221
+ ports=ports,
222
+ volumes=volumes,
223
+ remove_previous=self.interactive,
224
+ )
225
+ self.running_container = self.docker.attach_and_run(container)
226
+ assert self.running_container is not None, "Container should be running"
227
+ print("Compile server starting")
228
+ return port
229
+ else:
230
+ self.docker.run_container_interactive(
231
+ image_name=_IMAGE_NAME,
232
+ tag="main",
233
+ container_name=self.container_name,
234
+ command=cmd_str,
235
+ ports=ports,
236
+ volumes=volumes,
237
+ )
238
+
239
+ print("Exiting interactive mode")
240
+ return port
241
+
242
+ def process_running(self) -> bool:
243
+ return self.docker.is_container_running(self.container_name)
244
+
245
+ def stop(self) -> None:
246
+ if self.running_container:
247
+ self.running_container.detach()
248
+ self.running_container = None
249
+ self.docker.suspend_container(self.container_name)
250
+ self._port = 0
251
+ print("Compile server stopped")