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 +1 -1
- fastled/project_init.py +129 -129
- fastled/server_fastapi_cli.py +1 -1
- fastled/server_flask.py +33 -9
- fastled/server_start.py +60 -15
- fastled/site/build.py +449 -449
- {fastled-1.2.62.dist-info → fastled-1.2.63.dist-info}/METADATA +400 -400
- {fastled-1.2.62.dist-info → fastled-1.2.63.dist-info}/RECORD +12 -12
- {fastled-1.2.62.dist-info → fastled-1.2.63.dist-info}/WHEEL +1 -1
- {fastled-1.2.62.dist-info → fastled-1.2.63.dist-info}/entry_points.txt +0 -0
- {fastled-1.2.62.dist-info → fastled-1.2.63.dist-info}/licenses/LICENSE +0 -0
- {fastled-1.2.62.dist-info → fastled-1.2.63.dist-info}/top_level.txt +0 -0
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.
|
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()
|
fastled/server_fastapi_cli.py
CHANGED
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(
|
9
|
-
|
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(
|
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,
|
109
|
-
keyfile: Path | None = None,
|
132
|
+
certfile: Path | None = None,
|
133
|
+
keyfile: Path | None = None,
|
110
134
|
) -> Process:
|
111
|
-
"""Run the
|
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.
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
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
|
-
) ->
|
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
|
-
|
43
|
-
except KeyboardInterrupt:
|
44
|
-
|
61
|
+
# try:
|
62
|
+
# proc.join()
|
63
|
+
# except KeyboardInterrupt:
|
64
|
+
# import _thread
|
45
65
|
|
46
|
-
|
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() ->
|
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
|
-
|
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
|
-
|
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__":
|