fastled 1.3.1__py3-none-any.whl → 1.3.2__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/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
@@ -26,6 +26,16 @@ else:
26
26
  logger.disabled = True
27
27
 
28
28
 
29
+ def _is_dwarf_source(path: str) -> bool:
30
+ """Check if the path is a dwarf source file."""
31
+ # Check if the path starts with "fastledsource/" or "sketchsource/"
32
+ return (
33
+ path.startswith("fastledsource/")
34
+ or path.startswith("sketchsource/")
35
+ or path.startswith("dwarfsource")
36
+ )
37
+
38
+
29
39
  def _run_flask_server(
30
40
  fastled_js: Path,
31
41
  port: int,
@@ -137,13 +147,6 @@ def _run_flask_server(
137
147
 
138
148
  start_time = time.time()
139
149
  logger.info(f"Processing request: {request.method} {request.url}")
140
-
141
- if "../" in path:
142
- # Prevent directory traversal attacks
143
- error_msg = "Directory traversal attack detected"
144
- logger.error(error_msg)
145
- return Response(error_msg, status=400)
146
-
147
150
  # Forward the request to the compile server
148
151
  target_url = f"http://localhost:{compile_server_port}/{path}"
149
152
  logger.info(f"Requesting: {target_url}")
@@ -319,9 +322,8 @@ def _run_flask_server(
319
322
  logger.info(f"Received request for path: {path}")
320
323
 
321
324
  try:
322
- if path.startswith("fastledsource/") or path.startswith(
323
- "sketchsource/"
324
- ):
325
+ is_debug_src_code_request = _is_dwarf_source(path)
326
+ if is_debug_src_code_request:
325
327
  logger.info(f"Handling as drawfsource: {path}")
326
328
  return handle_fastledsource(path)
327
329
  elif path.startswith("sourcefiles/"):