fastled 1.2.67__py3-none-any.whl → 1.2.68__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.68"
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/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,6 +1,7 @@
1
1
  import subprocess
2
2
  import sys
3
3
  import time
4
+ import traceback
4
5
  import warnings
5
6
  from datetime import datetime, timezone
6
7
  from pathlib import Path
@@ -111,14 +112,24 @@ class CompileServerImpl:
111
112
  project_init(example=example, outputdir=outputdir)
112
113
 
113
114
  @property
114
- def running(self) -> bool:
115
+ def running(self) -> tuple[bool, Exception | None]:
115
116
  if not self._port:
116
- return False
117
+ return False, Exception("Docker hasn't been initialzed with a port yet")
117
118
  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)
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")
122
133
 
123
134
  def using_fastled_src_dir_volume(self) -> bool:
124
135
  out = self.fastled_src_dir is not None
fastled/docker_manager.py CHANGED
@@ -201,24 +201,24 @@ class DockerManager:
201
201
  return False
202
202
 
203
203
  @staticmethod
204
- def is_running() -> bool:
204
+ def is_running() -> tuple[bool, Exception | None]:
205
205
  """Check if Docker is running by pinging the Docker daemon."""
206
206
 
207
207
  if not DockerManager.is_docker_installed():
208
208
  print("Docker is not installed.")
209
- return False
209
+ return False, Exception("Docker is not installed.")
210
210
  try:
211
211
  # self.client.ping()
212
212
  client = docker.from_env()
213
213
  client.ping()
214
214
  print("Docker is running.")
215
- return True
215
+ return True, None
216
216
  except DockerException as e:
217
217
  print(f"Docker is not running: {str(e)}")
218
- return False
218
+ return False, e
219
219
  except Exception as e:
220
220
  print(f"Error pinging Docker daemon: {str(e)}")
221
- return False
221
+ return False, e
222
222
 
223
223
  def start(self) -> bool:
224
224
  """Attempt to start Docker Desktop (or the Docker daemon) automatically."""
fastled/project_init.py CHANGED
@@ -1,129 +1,129 @@
1
- import _thread
2
- import threading
3
- import time
4
- import zipfile
5
- from pathlib import Path
6
-
7
- import httpx
8
-
9
- from fastled.settings import DEFAULT_URL
10
- from fastled.spinner import Spinner
11
-
12
- DEFAULT_EXAMPLE = "wasm"
13
-
14
-
15
- def get_examples(host: str | None = None) -> list[str]:
16
- host = host or DEFAULT_URL
17
- url_info = f"{host}/info"
18
- response = httpx.get(url_info, timeout=4)
19
- response.raise_for_status()
20
- examples: list[str] = response.json()["examples"]
21
- return sorted(examples)
22
-
23
-
24
- def _prompt_for_example() -> str:
25
- examples = get_examples()
26
- while True:
27
- print("Available examples:")
28
- for i, example in enumerate(examples):
29
- print(f" [{i+1}]: {example}")
30
- answer = input("Enter the example number or name: ").strip()
31
- if answer.isdigit():
32
- example_num = int(answer) - 1
33
- if example_num < 0 or example_num >= len(examples):
34
- print("Invalid example number")
35
- continue
36
- return examples[example_num]
37
- elif answer in examples:
38
- return answer
39
-
40
-
41
- class DownloadThread(threading.Thread):
42
- def __init__(self, url: str, json: str):
43
- super().__init__(daemon=True)
44
- self.url = url
45
- self.json = json
46
- self.bytes_downloaded = 0
47
- self.content: bytes | None = None
48
- self.error: Exception | None = None
49
- self.success = False
50
-
51
- def run(self) -> None:
52
- timeout = httpx.Timeout(5.0, connect=5.0, read=120.0, write=30.0)
53
- try:
54
- with httpx.Client(timeout=timeout) as client:
55
- with client.stream("POST", self.url, json=self.json) as response:
56
- response.raise_for_status()
57
- content = b""
58
- for chunk in response.iter_bytes():
59
- content += chunk
60
- self.bytes_downloaded += len(chunk)
61
- self.content = content
62
- self.success = True
63
- except KeyboardInterrupt:
64
- self.error = RuntimeError("Download cancelled")
65
- _thread.interrupt_main()
66
- except Exception as e:
67
- self.error = e
68
-
69
-
70
- def project_init(
71
- example: str | None = "PROMPT", # prompt for example
72
- outputdir: Path | None = None,
73
- host: str | None = None,
74
- ) -> Path:
75
- """
76
- Initialize a new FastLED project.
77
- """
78
- host = host or DEFAULT_URL
79
- outputdir = Path(outputdir) if outputdir is not None else Path("fastled")
80
- outputdir.mkdir(exist_ok=True, parents=True)
81
- if example == "PROMPT" or example is None:
82
- try:
83
- example = _prompt_for_example()
84
- except httpx.HTTPStatusError:
85
- print(
86
- f"Failed to fetch examples, using default example '{DEFAULT_EXAMPLE}'"
87
- )
88
- example = DEFAULT_EXAMPLE
89
- assert example is not None
90
- endpoint_url = f"{host}/project/init"
91
- json = example
92
- print(f"Initializing project with example '{example}', url={endpoint_url}")
93
-
94
- # Start download thread
95
- download_thread = DownloadThread(endpoint_url, json)
96
- # spinner = Spinner("Downloading project...")
97
- with Spinner(f"Downloading project {example}..."):
98
- download_thread.start()
99
- while download_thread.is_alive():
100
- time.sleep(0.1)
101
-
102
- print() # New line after progress
103
- download_thread.join()
104
-
105
- # Check for errors
106
- if not download_thread.success:
107
- assert download_thread.error is not None
108
- raise download_thread.error
109
-
110
- content = download_thread.content
111
- assert content is not None
112
- tmpzip = outputdir / "fastled.zip"
113
- outputdir.mkdir(exist_ok=True)
114
- tmpzip.write_bytes(content)
115
- with zipfile.ZipFile(tmpzip, "r") as zip_ref:
116
- zip_ref.extractall(outputdir)
117
- tmpzip.unlink()
118
- out = outputdir / example
119
- print(f"Project initialized at {out}")
120
- assert out.exists()
121
- return out
122
-
123
-
124
- def unit_test() -> None:
125
- project_init()
126
-
127
-
128
- if __name__ == "__main__":
129
- unit_test()
1
+ import _thread
2
+ import threading
3
+ import time
4
+ import zipfile
5
+ from pathlib import Path
6
+
7
+ import httpx
8
+
9
+ from fastled.settings import DEFAULT_URL
10
+ from fastled.spinner import Spinner
11
+
12
+ DEFAULT_EXAMPLE = "wasm"
13
+
14
+
15
+ def get_examples(host: str | None = None) -> list[str]:
16
+ host = host or DEFAULT_URL
17
+ url_info = f"{host}/info"
18
+ response = httpx.get(url_info, timeout=4)
19
+ response.raise_for_status()
20
+ examples: list[str] = response.json()["examples"]
21
+ return sorted(examples)
22
+
23
+
24
+ def _prompt_for_example() -> str:
25
+ examples = get_examples()
26
+ while True:
27
+ print("Available examples:")
28
+ for i, example in enumerate(examples):
29
+ print(f" [{i+1}]: {example}")
30
+ answer = input("Enter the example number or name: ").strip()
31
+ if answer.isdigit():
32
+ example_num = int(answer) - 1
33
+ if example_num < 0 or example_num >= len(examples):
34
+ print("Invalid example number")
35
+ continue
36
+ return examples[example_num]
37
+ elif answer in examples:
38
+ return answer
39
+
40
+
41
+ class DownloadThread(threading.Thread):
42
+ def __init__(self, url: str, json: str):
43
+ super().__init__(daemon=True)
44
+ self.url = url
45
+ self.json = json
46
+ self.bytes_downloaded = 0
47
+ self.content: bytes | None = None
48
+ self.error: Exception | None = None
49
+ self.success = False
50
+
51
+ def run(self) -> None:
52
+ timeout = httpx.Timeout(5.0, connect=5.0, read=120.0, write=30.0)
53
+ try:
54
+ with httpx.Client(timeout=timeout) as client:
55
+ with client.stream("POST", self.url, json=self.json) as response:
56
+ response.raise_for_status()
57
+ content = b""
58
+ for chunk in response.iter_bytes():
59
+ content += chunk
60
+ self.bytes_downloaded += len(chunk)
61
+ self.content = content
62
+ self.success = True
63
+ except KeyboardInterrupt:
64
+ self.error = RuntimeError("Download cancelled")
65
+ _thread.interrupt_main()
66
+ except Exception as e:
67
+ self.error = e
68
+
69
+
70
+ def project_init(
71
+ example: str | None = "PROMPT", # prompt for example
72
+ outputdir: Path | None = None,
73
+ host: str | None = None,
74
+ ) -> Path:
75
+ """
76
+ Initialize a new FastLED project.
77
+ """
78
+ host = host or DEFAULT_URL
79
+ outputdir = Path(outputdir) if outputdir is not None else Path("fastled")
80
+ outputdir.mkdir(exist_ok=True, parents=True)
81
+ if example == "PROMPT" or example is None:
82
+ try:
83
+ example = _prompt_for_example()
84
+ except httpx.HTTPStatusError:
85
+ print(
86
+ f"Failed to fetch examples, using default example '{DEFAULT_EXAMPLE}'"
87
+ )
88
+ example = DEFAULT_EXAMPLE
89
+ assert example is not None
90
+ endpoint_url = f"{host}/project/init"
91
+ json = example
92
+ print(f"Initializing project with example '{example}', url={endpoint_url}")
93
+
94
+ # Start download thread
95
+ download_thread = DownloadThread(endpoint_url, json)
96
+ # spinner = Spinner("Downloading project...")
97
+ with Spinner(f"Downloading project {example}..."):
98
+ download_thread.start()
99
+ while download_thread.is_alive():
100
+ time.sleep(0.1)
101
+
102
+ print() # New line after progress
103
+ download_thread.join()
104
+
105
+ # Check for errors
106
+ if not download_thread.success:
107
+ assert download_thread.error is not None
108
+ raise download_thread.error
109
+
110
+ content = download_thread.content
111
+ assert content is not None
112
+ tmpzip = outputdir / "fastled.zip"
113
+ outputdir.mkdir(exist_ok=True)
114
+ tmpzip.write_bytes(content)
115
+ with zipfile.ZipFile(tmpzip, "r") as zip_ref:
116
+ zip_ref.extractall(outputdir)
117
+ tmpzip.unlink()
118
+ out = outputdir / example
119
+ print(f"Project initialized at {out}")
120
+ assert out.exists()
121
+ return out
122
+
123
+
124
+ def unit_test() -> None:
125
+ project_init()
126
+
127
+
128
+ if __name__ == "__main__":
129
+ unit_test()
fastled/server_flask.py CHANGED
@@ -1,152 +1,152 @@
1
- import argparse
2
- from multiprocessing import Process
3
- from pathlib import Path
4
-
5
- from livereload import Server
6
-
7
-
8
- def _run_flask_server(
9
- fastled_js: Path,
10
- port: int,
11
- certfile: Path | None = None,
12
- keyfile: Path | None = None,
13
- ) -> None:
14
- """Run Flask server with live reload in a subprocess
15
-
16
- Args:
17
- fastled_js: Path to the fastled_js directory
18
- port: Port to run the server on
19
- certfile: Path to the SSL certificate file
20
- keyfile: Path to the SSL key file
21
- """
22
- try:
23
- from flask import Flask, send_from_directory
24
-
25
- app = Flask(__name__)
26
-
27
- # Must be a full path or flask will fail to find the file.
28
- fastled_js = fastled_js.resolve()
29
-
30
- @app.route("/")
31
- def serve_index():
32
- return send_from_directory(fastled_js, "index.html")
33
-
34
- @app.route("/<path:path>")
35
- def serve_files(path):
36
- response = send_from_directory(fastled_js, path)
37
- # Some servers don't set the Content-Type header for a bunch of files.
38
- if path.endswith(".js"):
39
- response.headers["Content-Type"] = "application/javascript"
40
- if path.endswith(".css"):
41
- response.headers["Content-Type"] = "text/css"
42
- if path.endswith(".wasm"):
43
- response.headers["Content-Type"] = "application/wasm"
44
- if path.endswith(".json"):
45
- response.headers["Content-Type"] = "application/json"
46
- if path.endswith(".png"):
47
- response.headers["Content-Type"] = "image/png"
48
- if path.endswith(".jpg"):
49
- response.headers["Content-Type"] = "image/jpeg"
50
- if path.endswith(".jpeg"):
51
- response.headers["Content-Type"] = "image/jpeg"
52
- if path.endswith(".gif"):
53
- response.headers["Content-Type"] = "image/gif"
54
- if path.endswith(".svg"):
55
- response.headers["Content-Type"] = "image/svg+xml"
56
- if path.endswith(".ico"):
57
- response.headers["Content-Type"] = "image/x-icon"
58
- if path.endswith(".html"):
59
- response.headers["Content-Type"] = "text/html"
60
-
61
- # now also add headers to force no caching
62
- response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
63
- response.headers["Pragma"] = "no-cache"
64
- response.headers["Expires"] = "0"
65
- return response
66
-
67
- server = Server(app.wsgi_app)
68
- # Watch index.html for changes
69
- server.watch(str(fastled_js / "index.html"))
70
- # server.watch(str(fastled_js / "index.js"))
71
- # server.watch(str(fastled_js / "index.css"))
72
- # Start the server
73
- server.serve(port=port, debug=True)
74
- except KeyboardInterrupt:
75
- import _thread
76
-
77
- _thread.interrupt_main()
78
- except Exception as e:
79
- print(f"Failed to run Flask server: {e}")
80
- import _thread
81
-
82
- _thread.interrupt_main()
83
-
84
-
85
- def run(
86
- port: int, cwd: Path, certfile: Path | None = None, keyfile: Path | None = None
87
- ) -> None:
88
- """Run the Flask server."""
89
- try:
90
- _run_flask_server(cwd, port, certfile, keyfile)
91
- import warnings
92
-
93
- warnings.warn("Flask server has stopped")
94
- except KeyboardInterrupt:
95
- import _thread
96
-
97
- _thread.interrupt_main()
98
- pass
99
-
100
-
101
- def parse_args() -> argparse.Namespace:
102
- """Parse the command line arguments."""
103
- parser = argparse.ArgumentParser(
104
- description="Open a browser to the fastled_js directory"
105
- )
106
- parser.add_argument(
107
- "fastled_js", type=Path, help="Path to the fastled_js directory"
108
- )
109
- parser.add_argument(
110
- "--port",
111
- "-p",
112
- type=int,
113
- required=True,
114
- help="Port to run the server on (default: %(default)s)",
115
- )
116
- parser.add_argument(
117
- "--certfile",
118
- type=Path,
119
- help="Path to the SSL certificate file for HTTPS",
120
- )
121
- parser.add_argument(
122
- "--keyfile",
123
- type=Path,
124
- help="Path to the SSL key file for HTTPS",
125
- )
126
- return parser.parse_args()
127
-
128
-
129
- def run_flask_server_process(
130
- port: int,
131
- cwd: Path | None = None,
132
- certfile: Path | None = None,
133
- keyfile: Path | None = None,
134
- ) -> Process:
135
- """Run the Flask server in a separate process."""
136
- cwd = cwd or Path(".")
137
- process = Process(
138
- target=run,
139
- args=(port, cwd, certfile, keyfile),
140
- )
141
- process.start()
142
- return process
143
-
144
-
145
- def main() -> None:
146
- """Main function."""
147
- args = parse_args()
148
- run(args.port, args.fastled_js, args.certfile, args.keyfile)
149
-
150
-
151
- if __name__ == "__main__":
152
- main()
1
+ import argparse
2
+ from multiprocessing import Process
3
+ from pathlib import Path
4
+
5
+ from livereload import Server
6
+
7
+
8
+ def _run_flask_server(
9
+ fastled_js: Path,
10
+ port: int,
11
+ certfile: Path | None = None,
12
+ keyfile: Path | None = None,
13
+ ) -> None:
14
+ """Run Flask server with live reload in a subprocess
15
+
16
+ Args:
17
+ fastled_js: Path to the fastled_js directory
18
+ port: Port to run the server on
19
+ certfile: Path to the SSL certificate file
20
+ keyfile: Path to the SSL key file
21
+ """
22
+ try:
23
+ from flask import Flask, send_from_directory
24
+
25
+ app = Flask(__name__)
26
+
27
+ # Must be a full path or flask will fail to find the file.
28
+ fastled_js = fastled_js.resolve()
29
+
30
+ @app.route("/")
31
+ def serve_index():
32
+ return send_from_directory(fastled_js, "index.html")
33
+
34
+ @app.route("/<path:path>")
35
+ def serve_files(path):
36
+ response = send_from_directory(fastled_js, path)
37
+ # Some servers don't set the Content-Type header for a bunch of files.
38
+ if path.endswith(".js"):
39
+ response.headers["Content-Type"] = "application/javascript"
40
+ if path.endswith(".css"):
41
+ response.headers["Content-Type"] = "text/css"
42
+ if path.endswith(".wasm"):
43
+ response.headers["Content-Type"] = "application/wasm"
44
+ if path.endswith(".json"):
45
+ response.headers["Content-Type"] = "application/json"
46
+ if path.endswith(".png"):
47
+ response.headers["Content-Type"] = "image/png"
48
+ if path.endswith(".jpg"):
49
+ response.headers["Content-Type"] = "image/jpeg"
50
+ if path.endswith(".jpeg"):
51
+ response.headers["Content-Type"] = "image/jpeg"
52
+ if path.endswith(".gif"):
53
+ response.headers["Content-Type"] = "image/gif"
54
+ if path.endswith(".svg"):
55
+ response.headers["Content-Type"] = "image/svg+xml"
56
+ if path.endswith(".ico"):
57
+ response.headers["Content-Type"] = "image/x-icon"
58
+ if path.endswith(".html"):
59
+ response.headers["Content-Type"] = "text/html"
60
+
61
+ # now also add headers to force no caching
62
+ response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
63
+ response.headers["Pragma"] = "no-cache"
64
+ response.headers["Expires"] = "0"
65
+ return response
66
+
67
+ server = Server(app.wsgi_app)
68
+ # Watch index.html for changes
69
+ server.watch(str(fastled_js / "index.html"))
70
+ # server.watch(str(fastled_js / "index.js"))
71
+ # server.watch(str(fastled_js / "index.css"))
72
+ # Start the server
73
+ server.serve(port=port, debug=True)
74
+ except KeyboardInterrupt:
75
+ import _thread
76
+
77
+ _thread.interrupt_main()
78
+ except Exception as e:
79
+ print(f"Failed to run Flask server: {e}")
80
+ import _thread
81
+
82
+ _thread.interrupt_main()
83
+
84
+
85
+ def run(
86
+ port: int, cwd: Path, certfile: Path | None = None, keyfile: Path | None = None
87
+ ) -> None:
88
+ """Run the Flask server."""
89
+ try:
90
+ _run_flask_server(cwd, port, certfile, keyfile)
91
+ import warnings
92
+
93
+ warnings.warn("Flask server has stopped")
94
+ except KeyboardInterrupt:
95
+ import _thread
96
+
97
+ _thread.interrupt_main()
98
+ pass
99
+
100
+
101
+ def parse_args() -> argparse.Namespace:
102
+ """Parse the command line arguments."""
103
+ parser = argparse.ArgumentParser(
104
+ description="Open a browser to the fastled_js directory"
105
+ )
106
+ parser.add_argument(
107
+ "fastled_js", type=Path, help="Path to the fastled_js directory"
108
+ )
109
+ parser.add_argument(
110
+ "--port",
111
+ "-p",
112
+ type=int,
113
+ required=True,
114
+ help="Port to run the server on (default: %(default)s)",
115
+ )
116
+ parser.add_argument(
117
+ "--certfile",
118
+ type=Path,
119
+ help="Path to the SSL certificate file for HTTPS",
120
+ )
121
+ parser.add_argument(
122
+ "--keyfile",
123
+ type=Path,
124
+ help="Path to the SSL key file for HTTPS",
125
+ )
126
+ return parser.parse_args()
127
+
128
+
129
+ def run_flask_server_process(
130
+ port: int,
131
+ cwd: Path | None = None,
132
+ certfile: Path | None = None,
133
+ keyfile: Path | None = None,
134
+ ) -> Process:
135
+ """Run the Flask server in a separate process."""
136
+ cwd = cwd or Path(".")
137
+ process = Process(
138
+ target=run,
139
+ args=(port, cwd, certfile, keyfile),
140
+ )
141
+ process.start()
142
+ return process
143
+
144
+
145
+ def main() -> None:
146
+ """Main function."""
147
+ args = parse_args()
148
+ run(args.port, args.fastled_js, args.certfile, args.keyfile)
149
+
150
+
151
+ if __name__ == "__main__":
152
+ main()