fastled 1.2.62__py3-none-any.whl → 1.2.63__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.62"
16
+ __version__ = "1.2.63"
17
17
 
18
18
  DOCKER_FILE = (
19
19
  "https://raw.githubusercontent.com/zackees/fastled-wasm/refs/heads/main/Dockerfile"
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()
@@ -32,7 +32,7 @@ def _run_fastapi_server(
32
32
  "fastled.server_fastapi:app",
33
33
  host="127.0.0.1",
34
34
  port=port,
35
- reload=True,
35
+ reload=False,
36
36
  # reload_includes=["index.html"],
37
37
  ssl_certfile=certfile,
38
38
  ssl_keyfile=keyfile,
fastled/server_flask.py CHANGED
@@ -5,8 +5,20 @@ from pathlib import Path
5
5
  from livereload import Server
6
6
 
7
7
 
8
- def _run_flask_server(fastled_js: Path, port: int) -> None:
9
- """Run Flask server with live reload in a subprocess"""
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
+ """
10
22
  try:
11
23
  from flask import Flask, send_from_directory
12
24
 
@@ -70,10 +82,12 @@ def _run_flask_server(fastled_js: Path, port: int) -> None:
70
82
  _thread.interrupt_main()
71
83
 
72
84
 
73
- def run(port: int, cwd: Path) -> None:
85
+ def run(
86
+ port: int, cwd: Path, certfile: Path | None = None, keyfile: Path | None = None
87
+ ) -> None:
74
88
  """Run the Flask server."""
75
89
  try:
76
- _run_flask_server(cwd, port)
90
+ _run_flask_server(cwd, port, certfile, keyfile)
77
91
  import warnings
78
92
 
79
93
  warnings.warn("Flask server has stopped")
@@ -99,20 +113,30 @@ def parse_args() -> argparse.Namespace:
99
113
  required=True,
100
114
  help="Port to run the server on (default: %(default)s)",
101
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
+ )
102
126
  return parser.parse_args()
103
127
 
104
128
 
105
129
  def run_flask_server_process(
106
130
  port: int,
107
131
  cwd: Path | None = None,
108
- certfile: Path | None = None, # type: ignore[unused-argument]
109
- keyfile: Path | None = None, # type: ignore[unused-argument]
132
+ certfile: Path | None = None,
133
+ keyfile: Path | None = None,
110
134
  ) -> Process:
111
- """Run the FastAPI server in a separate process."""
135
+ """Run the Flask server in a separate process."""
112
136
  cwd = cwd or Path(".")
113
137
  process = Process(
114
138
  target=run,
115
- args=(port, cwd),
139
+ args=(port, cwd, certfile, keyfile),
116
140
  )
117
141
  process.start()
118
142
  return process
@@ -121,7 +145,7 @@ def run_flask_server_process(
121
145
  def main() -> None:
122
146
  """Main function."""
123
147
  args = parse_args()
124
- run(args.fastled_js, args.port)
148
+ run(args.port, args.fastled_js, args.certfile, args.keyfile)
125
149
 
126
150
 
127
151
  if __name__ == "__main__":
fastled/server_start.py CHANGED
@@ -1,14 +1,34 @@
1
1
  import argparse
2
2
  import importlib.resources as pkg_resources
3
+ from dataclasses import dataclass
4
+ from multiprocessing import Process
3
5
  from pathlib import Path
4
6
 
5
7
  from fastled.server_fastapi_cli import run_fastapi_server_process
6
8
  from fastled.server_flask import run_flask_server_process
7
9
 
8
- if True:
9
- run_server_process = run_flask_server_process
10
- else:
11
- run_server_process = run_fastapi_server_process
10
+
11
+ def run_server_process(
12
+ port: int, cwd: Path, certfile: Path | None = None, keyfile: Path | None = None
13
+ ) -> Process:
14
+ """Run the server in a separate process."""
15
+ if True:
16
+ # Use Flask server
17
+ process = run_flask_server_process(
18
+ port=port,
19
+ cwd=cwd,
20
+ certfile=certfile,
21
+ keyfile=keyfile,
22
+ )
23
+ else:
24
+ # Use FastAPI server
25
+ process = run_fastapi_server_process(
26
+ port=port,
27
+ cwd=cwd,
28
+ certfile=certfile,
29
+ keyfile=keyfile,
30
+ )
31
+ return process
12
32
 
13
33
 
14
34
  def get_asset_path(filename: str) -> Path | None:
@@ -22,12 +42,12 @@ def get_asset_path(filename: str) -> Path | None:
22
42
  return None
23
43
 
24
44
 
25
- def run(
45
+ def start_process(
26
46
  path: Path,
27
47
  port: int,
28
48
  certfile: Path | None = None,
29
49
  keyfile: Path | None = None,
30
- ) -> None:
50
+ ) -> Process:
31
51
  """Run the server, using package assets if explicit paths are not provided"""
32
52
  # Use package resources if no explicit path
33
53
  if certfile is None:
@@ -38,15 +58,24 @@ def run(
38
58
  # _run_flask_server(path, port, certfile, keyfile)
39
59
  # run_fastapi_server_process(port=port, path=path, certfile=certfile, keyfile=keyfile)
40
60
  proc = run_server_process(port=port, cwd=path)
41
- try:
42
- proc.join()
43
- except KeyboardInterrupt:
44
- import _thread
61
+ # try:
62
+ # proc.join()
63
+ # except KeyboardInterrupt:
64
+ # import _thread
45
65
 
46
- _thread.interrupt_main()
66
+ # _thread.interrupt_main()
67
+ return proc
68
+
69
+
70
+ @dataclass
71
+ class Args:
72
+ fastled_js: Path
73
+ port: int
74
+ cert: Path | None
75
+ key: Path | None
47
76
 
48
77
 
49
- def parse_args() -> argparse.Namespace:
78
+ def parse_args() -> Args:
50
79
  parser = argparse.ArgumentParser(
51
80
  description="Open a browser to the fastled_js directory"
52
81
  )
@@ -66,21 +95,37 @@ def parse_args() -> argparse.Namespace:
66
95
  parser.add_argument(
67
96
  "--key", type=Path, help="(Optional) Path to SSL private key (PEM format)"
68
97
  )
69
- return parser.parse_args()
98
+ args = parser.parse_args()
99
+ out: Args = Args(
100
+ fastled_js=args.fastled_js,
101
+ port=args.port,
102
+ cert=args.cert,
103
+ key=args.key,
104
+ )
105
+ if args.fastled_js is None:
106
+ raise ValueError("fastled_js directory is required")
107
+ return out
70
108
 
71
109
 
72
110
  def main() -> None:
73
- args = parse_args()
111
+ args: Args = parse_args()
74
112
  fastled_js: Path = args.fastled_js
75
113
  port: int = args.port
76
114
  cert: Path | None = args.cert
77
115
  key: Path | None = args.key
78
- run(
116
+ proc = start_process(
79
117
  path=fastled_js,
80
118
  port=port,
81
119
  certfile=cert,
82
120
  keyfile=key,
83
121
  )
122
+ try:
123
+ proc.join()
124
+ except KeyboardInterrupt:
125
+ import _thread
126
+
127
+ _thread.interrupt_main()
128
+ pass
84
129
 
85
130
 
86
131
  if __name__ == "__main__":