fastled 1.4.9__py3-none-any.whl → 1.4.11__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/__version__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # IMPORTANT! There's a bug in github which will REJECT any version update
2
2
  # that has any other change in the repo. Please bump the version as the
3
3
  # ONLY change in a commit, or else the pypi update and the release will fail.
4
- __version__ = "1.4.9"
4
+ __version__ = "1.4.11"
5
5
 
6
6
  __version_url_latest__ = "https://raw.githubusercontent.com/zackees/fastled-wasm/refs/heads/main/src/fastled/__version__.py"
@@ -0,0 +1,148 @@
1
+ """
2
+ Interruptible HTTP requests that can be cancelled with Ctrl+C.
3
+
4
+ This module provides cross-platform HTTP request functionality that can be
5
+ interrupted with Ctrl+C by using asyncio cancellation and periodic checks.
6
+ """
7
+
8
+ import asyncio
9
+
10
+ import httpx
11
+
12
+
13
+ class InterruptibleHTTPRequest:
14
+ """A wrapper for making HTTP requests that can be interrupted by Ctrl+C."""
15
+
16
+ def __init__(self):
17
+ self.cancelled = False
18
+
19
+ async def _make_request_async(
20
+ self,
21
+ url: str,
22
+ files: dict,
23
+ headers: dict,
24
+ transport: httpx.HTTPTransport | None = None,
25
+ timeout: float = 240,
26
+ follow_redirects: bool = True,
27
+ ) -> httpx.Response:
28
+ """Make an async HTTP request."""
29
+ # Convert sync transport to async transport if provided
30
+ async_transport = None
31
+ if transport is not None:
32
+ # For IPv4 connections, create async transport with local address
33
+ async_transport = httpx.AsyncHTTPTransport(local_address="0.0.0.0")
34
+
35
+ async with httpx.AsyncClient(
36
+ transport=async_transport,
37
+ timeout=timeout,
38
+ ) as client:
39
+ response = await client.post(
40
+ url,
41
+ follow_redirects=follow_redirects,
42
+ files=files,
43
+ headers=headers,
44
+ )
45
+ return response
46
+
47
+ def make_request_interruptible(
48
+ self,
49
+ url: str,
50
+ files: dict,
51
+ headers: dict,
52
+ transport: httpx.HTTPTransport | None = None,
53
+ timeout: float = 240,
54
+ follow_redirects: bool = True,
55
+ ) -> httpx.Response:
56
+ """Make an HTTP request that can be interrupted by Ctrl+C."""
57
+ try:
58
+ # Create a new event loop if we're not in one
59
+ try:
60
+ loop = asyncio.get_running_loop()
61
+ # We're already in an event loop, use run_in_executor
62
+ return asyncio.run_coroutine_threadsafe(
63
+ self._run_with_keyboard_check(
64
+ url, files, headers, transport, timeout, follow_redirects
65
+ ),
66
+ loop,
67
+ ).result()
68
+ except RuntimeError:
69
+ # No running loop, create one
70
+ return asyncio.run(
71
+ self._run_with_keyboard_check(
72
+ url, files, headers, transport, timeout, follow_redirects
73
+ )
74
+ )
75
+ except KeyboardInterrupt:
76
+ print("\nHTTP request cancelled by user")
77
+ raise
78
+
79
+ async def _run_with_keyboard_check(
80
+ self,
81
+ url: str,
82
+ files: dict,
83
+ headers: dict,
84
+ transport: httpx.HTTPTransport | None = None,
85
+ timeout: float = 240,
86
+ follow_redirects: bool = True,
87
+ ) -> httpx.Response:
88
+ """Run the request with periodic keyboard interrupt checks."""
89
+ task = asyncio.create_task(
90
+ self._make_request_async(
91
+ url, files, headers, transport, timeout, follow_redirects
92
+ )
93
+ )
94
+
95
+ # Poll for keyboard interrupt while waiting for the request
96
+ # This approach allows the task to be cancelled when KeyboardInterrupt
97
+ # is raised in the calling thread
98
+ while not task.done():
99
+ try:
100
+ # Wait for either completion or a short timeout
101
+ response = await asyncio.wait_for(asyncio.shield(task), timeout=0.1)
102
+ return response
103
+ except asyncio.TimeoutError:
104
+ # Continue waiting - the short timeout allows for more responsive
105
+ # cancellation when KeyboardInterrupt is raised
106
+ continue
107
+ except KeyboardInterrupt:
108
+ task.cancel()
109
+ print("\nHTTP request cancelled by user")
110
+ raise
111
+
112
+ return await task
113
+
114
+
115
+ def make_interruptible_post_request(
116
+ url: str,
117
+ files: dict | None = None,
118
+ headers: dict | None = None,
119
+ transport: httpx.HTTPTransport | None = None,
120
+ timeout: float = 240,
121
+ follow_redirects: bool = True,
122
+ ) -> httpx.Response:
123
+ """
124
+ Convenience function to make an interruptible POST request.
125
+
126
+ Args:
127
+ url: The URL to make the request to
128
+ files: Files to upload (optional)
129
+ headers: HTTP headers (optional)
130
+ transport: HTTP transport to use (optional)
131
+ timeout: Request timeout in seconds
132
+ follow_redirects: Whether to follow redirects
133
+
134
+ Returns:
135
+ The HTTP response
136
+
137
+ Raises:
138
+ KeyboardInterrupt: If the request was cancelled by Ctrl+C
139
+ """
140
+ request_handler = InterruptibleHTTPRequest()
141
+ return request_handler.make_request_interruptible(
142
+ url=url,
143
+ files=files or {},
144
+ headers=headers or {},
145
+ transport=transport,
146
+ timeout=timeout,
147
+ follow_redirects=follow_redirects,
148
+ )
fastled/open_browser.py CHANGED
@@ -6,7 +6,7 @@ import weakref
6
6
  from multiprocessing import Process
7
7
  from pathlib import Path
8
8
 
9
- from fastled.playwright_browser import open_with_playwright
9
+ from fastled.playwright.playwright_browser import open_with_playwright
10
10
  from fastled.server_flask import run_flask_in_thread
11
11
 
12
12
  # Global reference to keep Playwright browser alive
@@ -128,7 +128,9 @@ def spawn_http_server(
128
128
  if should_use_playwright:
129
129
  if app:
130
130
  # For --app mode, try to install browsers if needed
131
- from fastled.playwright_browser import install_playwright_browsers
131
+ from fastled.playwright.playwright_browser import (
132
+ install_playwright_browsers,
133
+ )
132
134
 
133
135
  install_playwright_browsers()
134
136
 
@@ -77,7 +77,7 @@ class PlaywrightBrowser:
77
77
  def _setup_extensions(self) -> None:
78
78
  """Setup Chrome extensions for enhanced debugging."""
79
79
  try:
80
- from fastled.chrome_extension_downloader import (
80
+ from fastled.playwright.chrome_extension_downloader import (
81
81
  download_cpp_devtools_extension,
82
82
  )
83
83
 
@@ -706,7 +706,7 @@ def install_playwright_browsers() -> bool:
706
706
 
707
707
  # Also download the C++ DevTools Support extension
708
708
  try:
709
- from fastled.chrome_extension_downloader import (
709
+ from fastled.playwright.chrome_extension_downloader import (
710
710
  download_cpp_devtools_extension,
711
711
  )
712
712
 
fastled/web_compile.py CHANGED
@@ -9,13 +9,14 @@ from pathlib import Path
9
9
  import httpx
10
10
 
11
11
  from fastled.find_good_connection import find_good_connection
12
+ from fastled.interruptible_http import make_interruptible_post_request
12
13
  from fastled.settings import SERVER_PORT
13
14
  from fastled.types import BuildMode, CompileResult
14
15
  from fastled.zip_files import ZipResult, zip_files
15
16
 
16
17
  DEFAULT_HOST = "https://fastled.onrender.com"
17
18
  ENDPOINT_COMPILED_WASM = "compile/wasm"
18
- _TIMEOUT = 60 * 4 # 2 mins timeout
19
+ _TIMEOUT = 60 * 4 # 4 mins timeout
19
20
  _AUTH_TOKEN = "oBOT5jbsO4ztgrpNsQwlmFLIKB"
20
21
 
21
22
 
@@ -29,6 +30,38 @@ def _sanitize_host(host: str) -> str:
29
30
  return host if host.startswith("http://") else f"http://{host}"
30
31
 
31
32
 
33
+ def _check_embedded_http_status(response_content: bytes) -> tuple[bool, int | None]:
34
+ """
35
+ Check if the response content has an embedded HTTP status at the end.
36
+
37
+ Returns:
38
+ tuple: (has_embedded_status, status_code)
39
+ has_embedded_status is True if an embedded status was found
40
+ status_code is the embedded status code or None if not found
41
+ """
42
+ try:
43
+ # Convert bytes to string for parsing
44
+ content_str = response_content.decode("utf-8", errors="ignore")
45
+
46
+ # Look for HTTP_STATUS: at the end of the content
47
+ lines = content_str.strip().split("\n")
48
+ if lines:
49
+ last_line = lines[-1].strip()
50
+ if last_line.startswith("HTTP_STATUS:"):
51
+ # Extract the status code
52
+ try:
53
+ status_code = int(last_line.split(":", 1)[1].strip())
54
+ return True, status_code
55
+ except (ValueError, IndexError):
56
+ # Malformed status line
57
+ return True, None
58
+
59
+ return False, None
60
+ except Exception:
61
+ # If we can't parse the content, assume no embedded status
62
+ return False, None
63
+
64
+
32
65
  def _banner(msg: str) -> str:
33
66
  """
34
67
  Create a banner for the given message.
@@ -83,25 +116,26 @@ def _compile_libfastled(
83
116
  httpx.HTTPTransport(local_address="0.0.0.0") if connection_result.ipv4 else None
84
117
  )
85
118
 
86
- with httpx.Client(
119
+ headers = {
120
+ "accept": "application/json",
121
+ "authorization": auth_token,
122
+ "build": build_mode.value.lower(),
123
+ }
124
+
125
+ url = f"{connection_result.host}/compile/libfastled"
126
+ print(f"Compiling libfastled on {url} via {ipv4_stmt}")
127
+
128
+ # Use interruptible HTTP request
129
+ response = make_interruptible_post_request(
130
+ url=url,
131
+ files={}, # No files for libfastled compilation
132
+ headers=headers,
87
133
  transport=transport,
88
134
  timeout=_TIMEOUT * 2, # Give more time for library compilation
89
- ) as client:
90
- headers = {
91
- "accept": "application/json",
92
- "authorization": auth_token,
93
- "build": build_mode.value.lower(),
94
- }
95
-
96
- url = f"{connection_result.host}/compile/libfastled"
97
- print(f"Compiling libfastled on {url} via {ipv4_stmt}")
98
- response = client.post(
99
- url,
100
- headers=headers,
101
- timeout=_TIMEOUT * 2,
102
- )
135
+ follow_redirects=False,
136
+ )
103
137
 
104
- return response
138
+ return response
105
139
 
106
140
 
107
141
  def _send_compile_request(
@@ -131,37 +165,32 @@ def _send_compile_request(
131
165
 
132
166
  archive_size = len(zip_bytes)
133
167
 
134
- with httpx.Client(
168
+ headers = {
169
+ "accept": "application/json",
170
+ "authorization": auth_token,
171
+ "build": (
172
+ build_mode.value.lower() if build_mode else BuildMode.QUICK.value.lower()
173
+ ),
174
+ "profile": "true" if profile else "false",
175
+ "no-platformio": "true" if no_platformio else "false",
176
+ "allow-libcompile": "false", # Always false since we handle it manually
177
+ }
178
+
179
+ url = f"{connection_result.host}/{ENDPOINT_COMPILED_WASM}"
180
+ print(f"Compiling sketch on {url} via {ipv4_stmt}. Zip size: {archive_size} bytes")
181
+ files = {"file": ("wasm.zip", zip_bytes, "application/x-zip-compressed")}
182
+
183
+ # Use interruptible HTTP request
184
+ response = make_interruptible_post_request(
185
+ url=url,
186
+ files=files,
187
+ headers=headers,
135
188
  transport=transport,
136
189
  timeout=_TIMEOUT,
137
- ) as client:
138
- headers = {
139
- "accept": "application/json",
140
- "authorization": auth_token,
141
- "build": (
142
- build_mode.value.lower()
143
- if build_mode
144
- else BuildMode.QUICK.value.lower()
145
- ),
146
- "profile": "true" if profile else "false",
147
- "no-platformio": "true" if no_platformio else "false",
148
- "allow-libcompile": "false", # Always false since we handle it manually
149
- }
150
-
151
- url = f"{connection_result.host}/{ENDPOINT_COMPILED_WASM}"
152
- print(
153
- f"Compiling sketch on {url} via {ipv4_stmt}. Zip size: {archive_size} bytes"
154
- )
155
- files = {"file": ("wasm.zip", zip_bytes, "application/x-zip-compressed")}
156
- response = client.post(
157
- url,
158
- follow_redirects=True,
159
- files=files,
160
- headers=headers,
161
- timeout=_TIMEOUT,
162
- )
190
+ follow_redirects=True,
191
+ )
163
192
 
164
- return response
193
+ return response
165
194
 
166
195
 
167
196
  def _process_compile_response(
@@ -264,13 +293,33 @@ def web_compile(
264
293
  print("Step 1: Compiling libfastled...")
265
294
  try:
266
295
  libfastled_response = _compile_libfastled(host, auth_token, build_mode)
296
+
297
+ # Check HTTP response status first
267
298
  if libfastled_response.status_code != 200:
268
299
  print(
269
- f"Warning: libfastled compilation failed with status {libfastled_response.status_code}"
300
+ f"Warning: libfastled compilation failed with HTTP status {libfastled_response.status_code}"
270
301
  )
271
302
  # Continue with sketch compilation even if libfastled fails
272
303
  else:
273
- print("✅ libfastled compilation successful")
304
+ # Check for embedded HTTP status in response content
305
+ has_embedded_status, embedded_status = _check_embedded_http_status(
306
+ libfastled_response.content
307
+ )
308
+ if has_embedded_status:
309
+ if embedded_status is not None and embedded_status != 200:
310
+ print(
311
+ f"Warning: libfastled compilation failed with embedded status {embedded_status}"
312
+ )
313
+ # Continue with sketch compilation even if libfastled fails
314
+ elif embedded_status is None:
315
+ print(
316
+ "Warning: libfastled compilation returned malformed embedded status"
317
+ )
318
+ # Continue with sketch compilation even if libfastled fails
319
+ else:
320
+ print("✅ libfastled compilation successful")
321
+ else:
322
+ print("✅ libfastled compilation successful")
274
323
  except Exception as e:
275
324
  print(f"Warning: libfastled compilation failed: {e}")
276
325
  # Continue with sketch compilation even if libfastled fails
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastled
3
- Version: 1.4.9
3
+ Version: 1.4.11
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies
@@ -1,9 +1,8 @@
1
1
  fastled/__init__.py,sha256=dahiY41HLLotTjqmpVJmXSwUEp8NKqoZ57jt55hBLa4,7667
2
2
  fastled/__main__.py,sha256=OcKv2ER1_iQAsZzLIUb3C8hRC9L2clNOhCrjpshrlf4,336
3
- fastled/__version__.py,sha256=ZiZk3FJ43XnaDFc89u1d82Mxs7giHFYqSujWneamokY,372
3
+ fastled/__version__.py,sha256=InZABLRZggZzcotBSOzvVJNzKcpNtCifyL7ZSI4-RDs,373
4
4
  fastled/app.py,sha256=6XOuObi72AUnZXASDOVbcSflr4He0xnIDk5P8nVmVus,6131
5
5
  fastled/args.py,sha256=d8Afa7NMcNLMIQqIBX_ZOL5JOyeJ7XCch4LdkhFNChk,3671
6
- fastled/chrome_extension_downloader.py,sha256=48YyQrsuK1TVXPuAvRGzqkQJnx0991Ka6OVUo1A58zU,7079
7
6
  fastled/cli.py,sha256=drgR2AOxVrj3QEz58iiKscYAumbbin2vIV-k91VCOAA,561
8
7
  fastled/cli_test.py,sha256=W-1nODZrip_JU6BEbYhxOa4ckxduOsiX8zIoRkTyxv4,550
9
8
  fastled/cli_test_interactive.py,sha256=BjNhveZOk5aCffHbcrxPQQjWmAuj4ClVKKcKX5eY6yM,542
@@ -13,13 +12,13 @@ fastled/compile_server_impl.py,sha256=iCwNCs7YxypUuVPmY4979mOgoH9OiuAJa1a1bmpG1c
13
12
  fastled/docker_manager.py,sha256=rkq39ZKrU6NHIyDa3mzs0Unb6o9oMeAwxhqiuHJU_RY,40291
14
13
  fastled/filewatcher.py,sha256=gEcJJHTDJ1X3gKJzltmEBhixWGbZj2eJD7a4vwSvITQ,10036
15
14
  fastled/find_good_connection.py,sha256=xnrJjrbwNZUkvSQRn_ZTMoVh5GBWTbO-lEsr_L95xq8,3372
15
+ fastled/interruptible_http.py,sha256=2QwUsRNJ1qawf_-Lp1l0dBady3TK0SrBFhmnWgM7oqg,4888
16
16
  fastled/keyboard.py,sha256=UTAsqCn1UMYnB8YDzENiLTj4GeL45tYfEcO7_5fLFEg,3556
17
17
  fastled/keyz.py,sha256=LO-8m_7CpNDiZLM-FXhQ30f9gN1bUYz5lOsUPTIbI-c,4020
18
18
  fastled/live_client.py,sha256=yp_ujG92EHYpSedGOUteuG2nQvMKbp1GbUpgQ6nU4Dc,3083
19
- fastled/open_browser.py,sha256=gtBF7hoc0qcmWM_om0crcJ26TsmX5sRTCulaVRQLjP8,5023
19
+ fastled/open_browser.py,sha256=N0d_D87sSBOUbBUl7gktJufdyM1laEKIERCXnEYCyu4,5086
20
20
  fastled/parse_args.py,sha256=UiGgFoR_7ZCVC26rxE4FpxpmPu9xBhiiCD4pwmY_gLw,11520
21
21
  fastled/paths.py,sha256=VsPmgu0lNSCFOoEC0BsTYzDygXqy15AHUfN-tTuzDZA,99
22
- fastled/playwright_browser.py,sha256=qsPQiwamSOSBuXPoqgr1ybIRZbjJH5MeoMNLjpR4aTg,26776
23
22
  fastled/print_filter.py,sha256=nc_rqYYdCUPinFycaK7fiQF5PG1up51pmJptR__QyAs,1499
24
23
  fastled/project_init.py,sha256=bBt4DwmW5hZkm9ICt9Qk-0Nr_0JQM7icCgH5Iv-bCQs,3984
25
24
  fastled/select_sketch_directory.py,sha256=-eudwCns3AKj4HuHtSkZAFwbnf005SNL07pOzs9VxnE,1383
@@ -32,18 +31,20 @@ fastled/string_diff.py,sha256=oTncu0qYdLlLUtYLLDB4bzdQ2OfzegAR6XNAzwE9fIs,6002
32
31
  fastled/types.py,sha256=ZDf1TbTT4XgA_pKIwr4JbkDB38_29ogSdDORjoT-zuY,1803
33
32
  fastled/util.py,sha256=TjhXbUNh4p2BGhNAldSeL68B7BBOjsWAXji5gy-vDEQ,1440
34
33
  fastled/version.py,sha256=TpBMiEVdO3_sUZEu6wmwN8Q4AgX2BiCxStCsnPKh6E0,1209
35
- fastled/web_compile.py,sha256=Ql2DBRInZy7dOr1WZiUlhdg1ZVuU1nkbndRWiq7iENQ,10002
34
+ fastled/web_compile.py,sha256=-wC9ECwcpeHWslaYBGU2Pyxw-3rVzcO8MS3mMbrxvAw,12284
36
35
  fastled/zip_files.py,sha256=BgHFjaLJ7wF6mnzjqOgn76VcKDwhwc_-w_qyUG_-aNs,2815
37
36
  fastled/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
38
37
  fastled/assets/localhost-key.pem,sha256=Q-CNO_UoOd8fFNN4ljcnqwUeCMhzTplRjLO2x0pYRlU,1704
39
38
  fastled/assets/localhost.pem,sha256=QTwUtTwjYWbm9m3pHW2IlK2nFZJ8b0pppxPjhgVZqQo,1619
39
+ fastled/playwright/chrome_extension_downloader.py,sha256=48YyQrsuK1TVXPuAvRGzqkQJnx0991Ka6OVUo1A58zU,7079
40
+ fastled/playwright/playwright_browser.py,sha256=5cx80kEV72CPlBT9fIcJNc9z40EFSOfXchBKBE9R4sE,26798
40
41
  fastled/site/build.py,sha256=2YKU_UWKlJdGnjdbAbaL0co6kceFMSTVYwH1KCmgPZA,13987
41
42
  fastled/site/examples.py,sha256=s6vj2zJc6BfKlnbwXr1QWY1mzuDBMt6j5MEBOWjO_U8,155
42
43
  fastled/test/can_run_local_docker_tests.py,sha256=LEuUbHctRhNNFWcvnz2kEGmjDJeXO4c3kNpizm3yVJs,400
43
44
  fastled/test/examples.py,sha256=GfaHeY1E8izBl6ZqDVjz--RHLyVR4NRnQ5pBesCFJFY,1673
44
- fastled-1.4.9.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
45
- fastled-1.4.9.dist-info/METADATA,sha256=y9Q00P53Nh_2KnIKWDMuKBrcq5_JqAp8MlmYf0PvwEw,31909
46
- fastled-1.4.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
- fastled-1.4.9.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
48
- fastled-1.4.9.dist-info/top_level.txt,sha256=Bbv5kpJpZhWNCvDF4K0VcvtBSDMa8B7PTOrZa9CezHY,8
49
- fastled-1.4.9.dist-info/RECORD,,
45
+ fastled-1.4.11.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
46
+ fastled-1.4.11.dist-info/METADATA,sha256=8E7b3fiwVxjjQMEXqV3LD0VOocn50otnANeJE_-eReY,31910
47
+ fastled-1.4.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
48
+ fastled-1.4.11.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
49
+ fastled-1.4.11.dist-info/top_level.txt,sha256=Bbv5kpJpZhWNCvDF4K0VcvtBSDMa8B7PTOrZa9CezHY,8
50
+ fastled-1.4.11.dist-info/RECORD,,