fastled 1.1.6__py2.py3-none-any.whl → 1.1.15__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/compile_server.py CHANGED
@@ -1,264 +1,221 @@
1
- import socket
2
- import subprocess
3
- import threading
4
- import time
5
- from pathlib import Path
6
- from typing import Optional
7
-
8
- import httpx
9
-
10
- from fastled.docker_manager import DockerManager
11
-
12
- _DEFAULT_CONTAINER_NAME = "fastled-wasm-compiler"
13
-
14
- SERVER_PORT = 9021
15
-
16
-
17
- def find_available_port(start_port: int = SERVER_PORT) -> int:
18
- """Find an available port starting from the given port."""
19
- port = start_port
20
- end_port = start_port + 1000
21
- while True:
22
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
23
- if s.connect_ex(("localhost", port)) != 0:
24
- return port
25
- port += 1
26
- if port >= end_port:
27
- raise RuntimeError("No available ports found")
28
-
29
-
30
- def looks_like_fastled_repo(directory: Path) -> bool:
31
- libprops = directory / "library.properties"
32
- if not libprops.exists():
33
- return False
34
- txt = libprops.read_text()
35
- return "FastLED" in txt
36
-
37
-
38
- class CompileServer:
39
- def __init__(
40
- self,
41
- container_name=_DEFAULT_CONTAINER_NAME,
42
- disable_auto_clean: bool = False,
43
- interactive: bool = False,
44
- ) -> None:
45
-
46
- cwd = Path(".").resolve()
47
- fastled_src_dir: Path | None = None
48
- if looks_like_fastled_repo(cwd):
49
- print(
50
- "Looks like a FastLED repo, using it as the source directory and mapping it into the server."
51
- )
52
- fastled_src_dir = cwd / "src"
53
-
54
- self.container_name = container_name
55
- self.disable_auto_clean = disable_auto_clean
56
- self.docker = DockerManager(container_name=container_name)
57
- self.running = False
58
- self.thread: Optional[threading.Thread] = None
59
- self.running_process: subprocess.Popen | None = None
60
- self.fastled_src_dir: Path | None = fastled_src_dir
61
- self.interactive = interactive
62
- self._port = self.start()
63
-
64
- def port(self) -> int:
65
- return self._port
66
-
67
- def url(self) -> str:
68
- return f"http://localhost:{self._port}"
69
-
70
- def wait_for_startup(self, timeout: int = 100) -> bool:
71
- """Wait for the server to start up."""
72
- start_time = time.time()
73
- while time.time() - start_time < timeout:
74
- # ping the server to see if it's up
75
- if not self._port:
76
- return False
77
- # use httpx to ping the server
78
- # if successful, return True
79
- try:
80
- response = httpx.get(
81
- f"http://localhost:{self._port}", follow_redirects=True
82
- )
83
- if response.status_code < 400:
84
- return True
85
- except KeyboardInterrupt:
86
- raise
87
- except Exception:
88
- pass
89
- time.sleep(0.1)
90
- if not self.running:
91
- return False
92
- return False
93
-
94
- def start(self) -> int:
95
- print("Compiling server starting")
96
- self.running = True
97
- # Ensure Docker is running
98
- with self.docker.get_lock():
99
- if not self.docker.is_running():
100
- if not self.docker.start():
101
- print("Docker could not be started. Exiting.")
102
- raise RuntimeError("Docker could not be started. Exiting.")
103
-
104
- # Clean up any existing container with the same name
105
-
106
- try:
107
- container_exists = (
108
- subprocess.run(
109
- ["docker", "inspect", self.container_name],
110
- capture_output=True,
111
- text=True,
112
- ).returncode
113
- == 0
114
- )
115
- if container_exists:
116
- print("Cleaning up existing container")
117
- subprocess.run(
118
- ["docker", "rm", "-f", self.container_name],
119
- check=False,
120
- )
121
- except KeyboardInterrupt:
122
- raise
123
- except Exception as e:
124
- print(f"Warning: Failed to remove existing container: {e}")
125
-
126
- print("Ensuring Docker image exists at latest version")
127
- if not self.docker.ensure_image_exists():
128
- print("Failed to ensure Docker image exists.")
129
- raise RuntimeError("Failed to ensure Docker image exists")
130
-
131
- print("Docker image now validated")
132
-
133
- # Remove the image to force a fresh download
134
- # subprocess.run(["docker", "rmi", "fastled-wasm"], capture_output=True)
135
- # print("All clean")
136
-
137
- port = find_available_port()
138
- print(f"Found an available port: {port}")
139
- # server_command = ["python", "/js/run.py", "server", "--allow-shutdown"]
140
- if self.interactive:
141
- server_command = ["/bin/bash"]
142
- else:
143
- server_command = ["python", "/js/run.py", "server"]
144
- if self.disable_auto_clean:
145
- server_command.append("--disable-auto-clean")
146
- print(f"Started Docker container with command: {server_command}")
147
- ports = {port: 80}
148
- volumes = None
149
- if self.fastled_src_dir:
150
- print(
151
- f"Mounting FastLED source directory {self.fastled_src_dir} into container /js/fastled/src"
152
- )
153
- volumes = {
154
- str(self.fastled_src_dir): {"bind": "/host/fastled/src", "mode": "ro"}
155
- }
156
- # no auto-update because the source directory is mapped in.
157
- server_command.append("--no-auto-update") # stop git repo updates.
158
- if not self.interactive:
159
- server_command.append(
160
- "--no-sketch-cache"
161
- ) # Remove sketch cache which assumes src is static.
162
- self.running_process = self.docker.run_container(
163
- server_command, ports=ports, volumes=volumes
164
- )
165
- print("Compile server starting")
166
- time.sleep(3)
167
- if self.running_process.poll() is not None:
168
- print("Server failed to start")
169
- self.running = False
170
- raise RuntimeError("Server failed to start")
171
- self.thread = threading.Thread(target=self._server_loop, daemon=True)
172
- self.thread.start()
173
-
174
- return port
175
-
176
- def proceess_running(self) -> bool:
177
- if self.running_process is None:
178
- return False
179
- return self.running_process.poll() is None
180
-
181
- def stop(self) -> None:
182
- print(f"Stopping server on port {self._port}")
183
- # attempt to send a shutdown signal to the server
184
- try:
185
- httpx.get(f"http://localhost:{self._port}/shutdown", timeout=2)
186
- # except Exception:
187
- except Exception as e:
188
- print(f"Failed to send shutdown signal: {e}")
189
- pass
190
- try:
191
- # Stop the Docker container
192
- cp: subprocess.CompletedProcess
193
- cp = subprocess.run(
194
- ["docker", "stop", self.container_name],
195
- capture_output=True,
196
- text=True,
197
- check=True,
198
- )
199
- if cp.returncode != 0:
200
- print(f"Failed to stop Docker container: {cp.stderr}")
201
-
202
- cp = subprocess.run(
203
- ["docker", "rm", self.container_name],
204
- capture_output=True,
205
- text=True,
206
- check=True,
207
- )
208
- if cp.returncode != 0:
209
- print(f"Failed to remove Docker container: {cp.stderr}")
210
-
211
- # Close the stdout pipe
212
- if self.running_process and self.running_process.stdout:
213
- self.running_process.stdout.close()
214
-
215
- # Wait for the process to fully terminate with a timeout
216
- self.running_process.wait(timeout=10)
217
- if self.running_process.returncode is None:
218
- # kill
219
- self.running_process.kill()
220
- if self.running_process.returncode is not None:
221
- print(
222
- f"Server stopped with return code {self.running_process.returncode}"
223
- )
224
-
225
- except subprocess.TimeoutExpired:
226
- # Force kill if it doesn't stop gracefully
227
- if self.running_process:
228
- self.running_process.kill()
229
- self.running_process.wait()
230
- except KeyboardInterrupt:
231
- if self.running_process:
232
- self.running_process.kill()
233
- self.running_process.wait()
234
- except Exception as e:
235
- print(f"Error stopping Docker container: {e}")
236
- finally:
237
- self.running_process = None
238
- # Signal the server thread to stop
239
- self.running = False
240
- if self.thread:
241
- self.thread.join(timeout=10) # Wait up to 10 seconds for thread to finish
242
- if self.thread.is_alive():
243
- print("Warning: Server thread did not terminate properly")
244
-
245
- print("Compile server stopped")
246
-
247
- def _server_loop(self) -> None:
248
- try:
249
- while self.running:
250
- if self.running_process:
251
- # Read Docker container output
252
- # Check if Docker process is still running
253
- if self.running_process.poll() is not None:
254
- print("Docker server stopped unexpectedly")
255
- self.running = False
256
- break
257
-
258
- time.sleep(0.1) # Prevent busy waiting
259
- except KeyboardInterrupt:
260
- print("Server thread stopped by user.")
261
- except Exception as e:
262
- print(f"Error in server thread: {e}")
263
- finally:
264
- self.running = False
1
+ import socket
2
+ import subprocess
3
+ import time
4
+ from pathlib import Path
5
+
6
+ import httpx
7
+
8
+ from fastled.docker_manager import DockerManager
9
+ from fastled.sketch import looks_like_fastled_repo
10
+
11
+ _DEFAULT_CONTAINER_NAME = "fastled-wasm-compiler"
12
+
13
+ SERVER_PORT = 9021
14
+
15
+ SERVER_OPTIONS = ["--allow-shutdown", "--no-auto-update"]
16
+
17
+
18
+ def find_available_port(start_port: int = SERVER_PORT) -> int:
19
+ """Find an available port starting from the given port."""
20
+ port = start_port
21
+ end_port = start_port + 1000
22
+ while True:
23
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
24
+ if s.connect_ex(("localhost", port)) != 0:
25
+ return port
26
+ port += 1
27
+ if port >= end_port:
28
+ raise RuntimeError("No available ports found")
29
+
30
+
31
+ class CompileServer:
32
+ def __init__(
33
+ self,
34
+ container_name=_DEFAULT_CONTAINER_NAME,
35
+ interactive: bool = False,
36
+ ) -> None:
37
+
38
+ cwd = Path(".").resolve()
39
+ fastled_src_dir: Path | None = None
40
+ if looks_like_fastled_repo(cwd):
41
+ print(
42
+ "Looks like a FastLED repo, using it as the source directory and mapping it into the server."
43
+ )
44
+ fastled_src_dir = cwd / "src"
45
+
46
+ self.container_name = container_name
47
+ self.docker = DockerManager(container_name=container_name)
48
+ self.running = False
49
+ self.running_process: subprocess.Popen | None = None
50
+ self.fastled_src_dir: Path | None = fastled_src_dir
51
+ self.interactive = interactive
52
+ self._port = self._start()
53
+ # fancy print
54
+ if not interactive:
55
+ msg = f"# FastLED Compile Server started at {self.url()} #"
56
+ print("\n" + "#" * len(msg))
57
+ print(msg)
58
+ print("#" * len(msg) + "\n")
59
+
60
+ def using_fastled_src_dir_volume(self) -> bool:
61
+ return self.fastled_src_dir is not None
62
+
63
+ def port(self) -> int:
64
+ return self._port
65
+
66
+ def url(self) -> str:
67
+ return f"http://localhost:{self._port}"
68
+
69
+ def wait_for_startup(self, timeout: int = 100) -> bool:
70
+ """Wait for the server to start up."""
71
+ start_time = time.time()
72
+ while time.time() - start_time < timeout:
73
+ # ping the server to see if it's up
74
+ if not self._port:
75
+ return False
76
+ # use httpx to ping the server
77
+ # if successful, return True
78
+ try:
79
+ response = httpx.get(
80
+ f"http://localhost:{self._port}", follow_redirects=True
81
+ )
82
+ if response.status_code < 400:
83
+ return True
84
+ except KeyboardInterrupt:
85
+ raise
86
+ except Exception:
87
+ pass
88
+ time.sleep(0.1)
89
+ if not self.running:
90
+ return False
91
+ return False
92
+
93
+ def _start(self) -> int:
94
+ print("Compiling server starting")
95
+ self.running = True
96
+ # Ensure Docker is running
97
+ with self.docker.get_lock():
98
+ if not self.docker.is_running():
99
+ if not self.docker.start():
100
+ print("Docker could not be started. Exiting.")
101
+ raise RuntimeError("Docker could not be started. Exiting.")
102
+
103
+ # Clean up any existing container with the same name
104
+ try:
105
+ container_exists = (
106
+ subprocess.run(
107
+ ["docker", "inspect", self.container_name],
108
+ capture_output=True,
109
+ text=True,
110
+ ).returncode
111
+ == 0
112
+ )
113
+ if container_exists:
114
+ print("Cleaning up existing container")
115
+ subprocess.run(
116
+ ["docker", "rm", "-f", self.container_name],
117
+ check=False,
118
+ )
119
+ except KeyboardInterrupt:
120
+ raise
121
+ except Exception as e:
122
+ print(f"Warning: Failed to remove existing container: {e}")
123
+
124
+ print("Ensuring Docker image exists at latest version")
125
+ if not self.docker.ensure_image_exists():
126
+ print("Failed to ensure Docker image exists.")
127
+ raise RuntimeError("Failed to ensure Docker image exists")
128
+
129
+ print("Docker image now validated")
130
+ port = find_available_port()
131
+ print(f"Found an available port: {port}")
132
+ if self.interactive:
133
+ server_command = ["/bin/bash"]
134
+ else:
135
+ server_command = ["python", "/js/run.py", "server"] + SERVER_OPTIONS
136
+ server_cmd_str = subprocess.list2cmdline(server_command)
137
+ print(f"Started Docker container with command: {server_cmd_str}")
138
+ ports = {port: 80}
139
+ volumes = None
140
+ if self.fastled_src_dir:
141
+ print(
142
+ f"Mounting FastLED source directory {self.fastled_src_dir} into container /host/fastled/src"
143
+ )
144
+ volumes = {
145
+ str(self.fastled_src_dir): {"bind": "/host/fastled/src", "mode": "ro"}
146
+ }
147
+ self.running_process = self.docker.run_container(
148
+ server_command, ports=ports, volumes=volumes, tty=self.interactive
149
+ )
150
+ print("Compile server starting")
151
+ time.sleep(3)
152
+ if self.running_process.poll() is not None:
153
+ print("Server failed to start")
154
+ self.running = False
155
+ raise RuntimeError("Server failed to start")
156
+ return port
157
+
158
+ def proceess_running(self) -> bool:
159
+ if self.running_process is None:
160
+ return False
161
+ return self.running_process.poll() is None
162
+
163
+ def stop(self) -> None:
164
+ print(f"Stopping server on port {self._port}")
165
+ # # attempt to send a shutdown signal to the server
166
+ # try:
167
+ # httpx.get(f"http://localhost:{self._port}/shutdown", timeout=2)
168
+ # # except Exception:
169
+ # except Exception as e:
170
+ # print(f"Failed to send shutdown signal: {e}")
171
+ # pass
172
+ try:
173
+ # Stop the Docker container
174
+ cp: subprocess.CompletedProcess
175
+ cp = subprocess.run(
176
+ ["docker", "stop", self.container_name],
177
+ capture_output=True,
178
+ text=True,
179
+ check=True,
180
+ )
181
+ if cp.returncode != 0:
182
+ print(f"Failed to stop Docker container: {cp.stderr}")
183
+
184
+ cp = subprocess.run(
185
+ ["docker", "rm", self.container_name],
186
+ capture_output=True,
187
+ text=True,
188
+ check=True,
189
+ )
190
+ if cp.returncode != 0:
191
+ print(f"Failed to remove Docker container: {cp.stderr}")
192
+
193
+ # Close the stdout pipe
194
+ if self.running_process and self.running_process.stdout:
195
+ self.running_process.stdout.close()
196
+
197
+ # Wait for the process to fully terminate with a timeout
198
+ self.running_process.wait(timeout=10)
199
+ if self.running_process.returncode is None:
200
+ # kill
201
+ self.running_process.kill()
202
+ if self.running_process.returncode is not None:
203
+ print(
204
+ f"Server stopped with return code {self.running_process.returncode}"
205
+ )
206
+
207
+ except subprocess.TimeoutExpired:
208
+ # Force kill if it doesn't stop gracefully
209
+ if self.running_process:
210
+ self.running_process.kill()
211
+ self.running_process.wait()
212
+ except KeyboardInterrupt:
213
+ if self.running_process:
214
+ self.running_process.kill()
215
+ self.running_process.wait()
216
+ except Exception as e:
217
+ print(f"Error stopping Docker container: {e}")
218
+ finally:
219
+ self.running_process = None
220
+ self.running = False
221
+ print("Compile server stopped")
fastled/docker_manager.py CHANGED
@@ -212,6 +212,7 @@ class DockerManager:
212
212
  cmd: list[str],
213
213
  volumes: dict[str, dict[str, str]] | None = None,
214
214
  ports: dict[int, int] | None = None,
215
+ tty: bool = False,
215
216
  ) -> subprocess.Popen:
216
217
  """Run the Docker container with the specified volume.
217
218
 
@@ -226,7 +227,8 @@ class DockerManager:
226
227
  print("Creating new container...")
227
228
  docker_command = ["docker", "run"]
228
229
 
229
- if sys.stdout.isatty():
230
+ if tty:
231
+ assert sys.stdout.isatty(), "TTY mode requires a TTY"
230
232
  docker_command.append("-it")
231
233
  # Attach volumes if specified
232
234
  docker_command += [