fastled 1.2.60__py3-none-any.whl → 1.2.62__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.60"
16
+ __version__ = "1.2.62"
17
17
 
18
18
  DOCKER_FILE = (
19
19
  "https://raw.githubusercontent.com/zackees/fastled-wasm/refs/heads/main/Dockerfile"
fastled/open_browser.py CHANGED
@@ -14,10 +14,9 @@ SSL_CONFIG = get_ssl_config()
14
14
  # print(f"SSL Config: {SSL_CONFIG.certfile}, {SSL_CONFIG.keyfile}")
15
15
 
16
16
 
17
- def open_http_server_subprocess(
17
+ def _open_http_server_subprocess(
18
18
  fastled_js: Path,
19
19
  port: int,
20
- open_browser: bool,
21
20
  ) -> None:
22
21
  print("\n################################################################")
23
22
  print(f"# Opening browser to {fastled_js} on port {port}")
@@ -27,13 +26,11 @@ def open_http_server_subprocess(
27
26
  cmd = [
28
27
  PYTHON_EXE,
29
28
  "-m",
30
- "fastled.open_browser2",
29
+ "fastled.server_start",
31
30
  str(fastled_js),
32
31
  "--port",
33
32
  str(port),
34
33
  ]
35
- if open_browser:
36
- cmd.append("--open-browser")
37
34
  # Pass SSL flags if available
38
35
  if SSL_CONFIG.certfile and SSL_CONFIG.keyfile:
39
36
  cmd.extend(
@@ -52,7 +49,7 @@ def open_http_server_subprocess(
52
49
  subprocess.run(
53
50
  cmd,
54
51
  stdout=subprocess.DEVNULL,
55
- stderr=subprocess.DEVNULL,
52
+ # stderr=subprocess.DEVNULL,
56
53
  ) # type ignore
57
54
  except KeyboardInterrupt:
58
55
  print("Exiting from server...")
@@ -111,21 +108,21 @@ def open_browser_process(
111
108
  port = find_free_port(DEFAULT_PORT)
112
109
 
113
110
  proc = Process(
114
- target=open_http_server_subprocess,
115
- args=(fastled_js, port, open_browser),
111
+ target=_open_http_server_subprocess,
112
+ args=(fastled_js, port),
116
113
  daemon=True,
117
114
  )
118
115
  proc.start()
119
116
  wait_for_server(port)
120
- # if open_browser:
121
- # print(
122
- # f"Opening browser to http{'s' if SSL_CONFIG.certfile else ''}://localhost:{port}"
123
- # )
124
- # webbrowser.open(
125
- # url=f"http{'s' if SSL_CONFIG.certfile else ''}://localhost:{port}",
126
- # new=1,
127
- # autoraise=True,
128
- # )
117
+ if open_browser:
118
+ print(f"Opening browser to http://localhost:{port}")
119
+ import webbrowser
120
+
121
+ webbrowser.open(
122
+ url=f"http://localhost:{port}",
123
+ new=1,
124
+ autoraise=True,
125
+ )
129
126
  return proc
130
127
 
131
128
 
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()
@@ -33,13 +33,13 @@ def _run_fastapi_server(
33
33
  host="127.0.0.1",
34
34
  port=port,
35
35
  reload=True,
36
- reload_includes=["index.html"],
36
+ # reload_includes=["index.html"],
37
37
  ssl_certfile=certfile,
38
38
  ssl_keyfile=keyfile,
39
39
  )
40
40
 
41
41
 
42
- def run_fastapi_server_proces(
42
+ def run_fastapi_server_process(
43
43
  port: int,
44
44
  cwd: Path | None = None,
45
45
  certfile: Path | None = None,
@@ -57,5 +57,5 @@ def run_fastapi_server_proces(
57
57
 
58
58
  if __name__ == "__main__":
59
59
  # Example usage
60
- proc = run_fastapi_server_proces(port=8000)
60
+ proc = run_fastapi_server_process(port=8000)
61
61
  proc.join()
@@ -0,0 +1,128 @@
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(fastled_js: Path, port: int) -> None:
9
+ """Run Flask server with live reload in a subprocess"""
10
+ try:
11
+ from flask import Flask, send_from_directory
12
+
13
+ app = Flask(__name__)
14
+
15
+ # Must be a full path or flask will fail to find the file.
16
+ fastled_js = fastled_js.resolve()
17
+
18
+ @app.route("/")
19
+ def serve_index():
20
+ return send_from_directory(fastled_js, "index.html")
21
+
22
+ @app.route("/<path:path>")
23
+ def serve_files(path):
24
+ response = send_from_directory(fastled_js, path)
25
+ # Some servers don't set the Content-Type header for a bunch of files.
26
+ if path.endswith(".js"):
27
+ response.headers["Content-Type"] = "application/javascript"
28
+ if path.endswith(".css"):
29
+ response.headers["Content-Type"] = "text/css"
30
+ if path.endswith(".wasm"):
31
+ response.headers["Content-Type"] = "application/wasm"
32
+ if path.endswith(".json"):
33
+ response.headers["Content-Type"] = "application/json"
34
+ if path.endswith(".png"):
35
+ response.headers["Content-Type"] = "image/png"
36
+ if path.endswith(".jpg"):
37
+ response.headers["Content-Type"] = "image/jpeg"
38
+ if path.endswith(".jpeg"):
39
+ response.headers["Content-Type"] = "image/jpeg"
40
+ if path.endswith(".gif"):
41
+ response.headers["Content-Type"] = "image/gif"
42
+ if path.endswith(".svg"):
43
+ response.headers["Content-Type"] = "image/svg+xml"
44
+ if path.endswith(".ico"):
45
+ response.headers["Content-Type"] = "image/x-icon"
46
+ if path.endswith(".html"):
47
+ response.headers["Content-Type"] = "text/html"
48
+
49
+ # now also add headers to force no caching
50
+ response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
51
+ response.headers["Pragma"] = "no-cache"
52
+ response.headers["Expires"] = "0"
53
+ return response
54
+
55
+ server = Server(app.wsgi_app)
56
+ # Watch index.html for changes
57
+ server.watch(str(fastled_js / "index.html"))
58
+ # server.watch(str(fastled_js / "index.js"))
59
+ # server.watch(str(fastled_js / "index.css"))
60
+ # Start the server
61
+ server.serve(port=port, debug=True)
62
+ except KeyboardInterrupt:
63
+ import _thread
64
+
65
+ _thread.interrupt_main()
66
+ except Exception as e:
67
+ print(f"Failed to run Flask server: {e}")
68
+ import _thread
69
+
70
+ _thread.interrupt_main()
71
+
72
+
73
+ def run(port: int, cwd: Path) -> None:
74
+ """Run the Flask server."""
75
+ try:
76
+ _run_flask_server(cwd, port)
77
+ import warnings
78
+
79
+ warnings.warn("Flask server has stopped")
80
+ except KeyboardInterrupt:
81
+ import _thread
82
+
83
+ _thread.interrupt_main()
84
+ pass
85
+
86
+
87
+ def parse_args() -> argparse.Namespace:
88
+ """Parse the command line arguments."""
89
+ parser = argparse.ArgumentParser(
90
+ description="Open a browser to the fastled_js directory"
91
+ )
92
+ parser.add_argument(
93
+ "fastled_js", type=Path, help="Path to the fastled_js directory"
94
+ )
95
+ parser.add_argument(
96
+ "--port",
97
+ "-p",
98
+ type=int,
99
+ required=True,
100
+ help="Port to run the server on (default: %(default)s)",
101
+ )
102
+ return parser.parse_args()
103
+
104
+
105
+ def run_flask_server_process(
106
+ port: int,
107
+ cwd: Path | None = None,
108
+ certfile: Path | None = None, # type: ignore[unused-argument]
109
+ keyfile: Path | None = None, # type: ignore[unused-argument]
110
+ ) -> Process:
111
+ """Run the FastAPI server in a separate process."""
112
+ cwd = cwd or Path(".")
113
+ process = Process(
114
+ target=run,
115
+ args=(port, cwd),
116
+ )
117
+ process.start()
118
+ return process
119
+
120
+
121
+ def main() -> None:
122
+ """Main function."""
123
+ args = parse_args()
124
+ run(args.fastled_js, args.port)
125
+
126
+
127
+ if __name__ == "__main__":
128
+ main()
@@ -2,7 +2,13 @@ import argparse
2
2
  import importlib.resources as pkg_resources
3
3
  from pathlib import Path
4
4
 
5
- from .server_fastapi_cli import run_fastapi_server_proces
5
+ from fastled.server_fastapi_cli import run_fastapi_server_process
6
+ from fastled.server_flask import run_flask_server_process
7
+
8
+ if True:
9
+ run_server_process = run_flask_server_process
10
+ else:
11
+ run_server_process = run_fastapi_server_process
6
12
 
7
13
 
8
14
  def get_asset_path(filename: str) -> Path | None:
@@ -16,28 +22,9 @@ def get_asset_path(filename: str) -> Path | None:
16
22
  return None
17
23
 
18
24
 
19
- def _open_browser(url: str) -> None:
20
- # import webview
21
-
22
- # print("\n##################################################")
23
- # print(f"# Opening browser to {url}")
24
- # print("##################################################\n")
25
-
26
- # webview.create_window("FastLED", url)
27
- # webview.start()
28
- import webbrowser
29
-
30
- webbrowser.open(url, new=1, autoraise=True)
31
- while True:
32
- import time
33
-
34
- time.sleep(1)
35
-
36
-
37
25
  def run(
38
26
  path: Path,
39
27
  port: int,
40
- open_browser: bool,
41
28
  certfile: Path | None = None,
42
29
  keyfile: Path | None = None,
43
30
  ) -> None:
@@ -49,10 +36,8 @@ def run(
49
36
  keyfile = get_asset_path("localhost-key.pem")
50
37
 
51
38
  # _run_flask_server(path, port, certfile, keyfile)
52
- # run_fastapi_server_proces(port=port, path=path, certfile=certfile, keyfile=keyfile)
53
- proc = run_fastapi_server_proces(port=port, cwd=path)
54
- if open_browser:
55
- _open_browser(f"http://localhost:{port}/")
39
+ # run_fastapi_server_process(port=port, path=path, certfile=certfile, keyfile=keyfile)
40
+ proc = run_server_process(port=port, cwd=path)
56
41
  try:
57
42
  proc.join()
58
43
  except KeyboardInterrupt:
@@ -81,16 +66,11 @@ def parse_args() -> argparse.Namespace:
81
66
  parser.add_argument(
82
67
  "--key", type=Path, help="(Optional) Path to SSL private key (PEM format)"
83
68
  )
84
- parser.add_argument(
85
- "--open-browser",
86
- action="store_true",
87
- )
88
69
  return parser.parse_args()
89
70
 
90
71
 
91
72
  def main() -> None:
92
73
  args = parse_args()
93
- open_browser: bool = args.open_browser
94
74
  fastled_js: Path = args.fastled_js
95
75
  port: int = args.port
96
76
  cert: Path | None = args.cert
@@ -98,7 +78,6 @@ def main() -> None:
98
78
  run(
99
79
  path=fastled_js,
100
80
  port=port,
101
- open_browser=open_browser,
102
81
  certfile=cert,
103
82
  keyfile=key,
104
83
  )