fastled 1.2.46__py3-none-any.whl → 1.2.48__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 -2
- fastled/client_server.py +405 -401
- fastled/docker_manager.py +15 -13
- fastled/filewatcher.py +16 -4
- fastled/project_init.py +129 -129
- fastled/site/build.py +457 -457
- {fastled-1.2.46.dist-info → fastled-1.2.48.dist-info}/METADATA +383 -384
- {fastled-1.2.46.dist-info → fastled-1.2.48.dist-info}/RECORD +12 -12
- {fastled-1.2.46.dist-info → fastled-1.2.48.dist-info}/LICENSE +0 -0
- {fastled-1.2.46.dist-info → fastled-1.2.48.dist-info}/WHEEL +0 -0
- {fastled-1.2.46.dist-info → fastled-1.2.48.dist-info}/entry_points.txt +0 -0
- {fastled-1.2.46.dist-info → fastled-1.2.48.dist-info}/top_level.txt +0 -0
fastled/docker_manager.py
CHANGED
@@ -18,6 +18,7 @@ import docker
|
|
18
18
|
from appdirs import user_data_dir
|
19
19
|
from disklru import DiskLRUCache
|
20
20
|
from docker.client import DockerClient
|
21
|
+
from docker.errors import DockerException, NotFound
|
21
22
|
from docker.models.containers import Container
|
22
23
|
from docker.models.images import Image
|
23
24
|
from filelock import FileLock
|
@@ -57,7 +58,9 @@ def get_lock(image_name: str) -> FileLock:
|
|
57
58
|
print(CONFIG_DIR)
|
58
59
|
if not lock_file.parent.exists():
|
59
60
|
lock_file.parent.mkdir(parents=True, exist_ok=True)
|
60
|
-
|
61
|
+
out: FileLock
|
62
|
+
out = FileLock(str(lock_file)) # type: ignore
|
63
|
+
return out
|
61
64
|
|
62
65
|
|
63
66
|
class RunningContainer:
|
@@ -181,10 +184,6 @@ class DockerManager:
|
|
181
184
|
)
|
182
185
|
return False
|
183
186
|
|
184
|
-
except subprocess.CalledProcessError as e:
|
185
|
-
print(f"Error occurred: {e}")
|
186
|
-
return False
|
187
|
-
|
188
187
|
except subprocess.CalledProcessError as e:
|
189
188
|
print(f"Failed to switch to Linux containers: {e}")
|
190
189
|
if e.stdout:
|
@@ -199,6 +198,7 @@ class DockerManager:
|
|
199
198
|
@staticmethod
|
200
199
|
def is_running() -> bool:
|
201
200
|
"""Check if Docker is running by pinging the Docker daemon."""
|
201
|
+
|
202
202
|
if not DockerManager.is_docker_installed():
|
203
203
|
return False
|
204
204
|
try:
|
@@ -207,7 +207,7 @@ class DockerManager:
|
|
207
207
|
client.ping()
|
208
208
|
print("Docker is running.")
|
209
209
|
return True
|
210
|
-
except
|
210
|
+
except DockerException as e:
|
211
211
|
print(f"Docker is not running: {str(e)}")
|
212
212
|
return False
|
213
213
|
except Exception as e:
|
@@ -289,8 +289,10 @@ class DockerManager:
|
|
289
289
|
remote_image_hash = remote_image.id
|
290
290
|
|
291
291
|
try:
|
292
|
+
local_image_id = local_image.id
|
293
|
+
assert local_image_id is not None
|
292
294
|
remote_image_hash_from_local_image = DISK_CACHE.get(
|
293
|
-
|
295
|
+
local_image_id
|
294
296
|
)
|
295
297
|
except KeyboardInterrupt:
|
296
298
|
raise
|
@@ -455,14 +457,14 @@ class DockerManager:
|
|
455
457
|
if remove_previous:
|
456
458
|
print(f"Removing existing container {container_name}...")
|
457
459
|
container.remove(force=True)
|
458
|
-
raise
|
460
|
+
raise NotFound("Container removed due to remove_previous")
|
459
461
|
# Check if configuration matches
|
460
462
|
elif not self._container_configs_match(container, command, volumes, ports):
|
461
463
|
print(
|
462
464
|
f"Container {container_name} exists but with different configuration. Removing and recreating..."
|
463
465
|
)
|
464
466
|
container.remove(force=True)
|
465
|
-
raise
|
467
|
+
raise NotFound("Container removed due to config mismatch")
|
466
468
|
print(f"Container {container_name} found with matching configuration.")
|
467
469
|
|
468
470
|
# Existing container with matching config - handle various states
|
@@ -492,7 +494,7 @@ class DockerManager:
|
|
492
494
|
print(f"Starting existing container {container_name}.")
|
493
495
|
self.first_run = True
|
494
496
|
container.start()
|
495
|
-
except
|
497
|
+
except NotFound:
|
496
498
|
print(f"Creating and starting {container_name}")
|
497
499
|
out_msg = f"# Running in container: {command}"
|
498
500
|
msg_len = len(out_msg)
|
@@ -524,7 +526,7 @@ class DockerManager:
|
|
524
526
|
try:
|
525
527
|
container: Container = self.client.containers.get(container_name)
|
526
528
|
container.remove(force=True)
|
527
|
-
except
|
529
|
+
except NotFound:
|
528
530
|
pass
|
529
531
|
start_time = time.time()
|
530
532
|
try:
|
@@ -615,7 +617,7 @@ class DockerManager:
|
|
615
617
|
"""
|
616
618
|
try:
|
617
619
|
return self.client.containers.get(container_name)
|
618
|
-
except
|
620
|
+
except NotFound:
|
619
621
|
return None
|
620
622
|
|
621
623
|
def is_container_running(self, container_name: str) -> bool:
|
@@ -625,7 +627,7 @@ class DockerManager:
|
|
625
627
|
try:
|
626
628
|
container = self.client.containers.get(container_name)
|
627
629
|
return container.status == "running"
|
628
|
-
except
|
630
|
+
except NotFound:
|
629
631
|
print(f"Container {container_name} not found.")
|
630
632
|
return False
|
631
633
|
|
fastled/filewatcher.py
CHANGED
@@ -15,6 +15,8 @@ from watchdog.events import FileSystemEvent, FileSystemEventHandler
|
|
15
15
|
from watchdog.observers import Observer
|
16
16
|
from watchdog.observers.api import BaseObserver
|
17
17
|
|
18
|
+
_WATCHER_TIMEOUT = 0.1
|
19
|
+
|
18
20
|
|
19
21
|
def file_watcher_enabled() -> bool:
|
20
22
|
"""Check if watchdog is disabled"""
|
@@ -120,7 +122,7 @@ class FileChangedNotifier(threading.Thread):
|
|
120
122
|
finally:
|
121
123
|
self.stop()
|
122
124
|
|
123
|
-
def get_next_change(self, timeout: float =
|
125
|
+
def get_next_change(self, timeout: float = _WATCHER_TIMEOUT) -> str | None:
|
124
126
|
"""Get the next file change event from the queue
|
125
127
|
|
126
128
|
Args:
|
@@ -148,7 +150,7 @@ class FileChangedNotifier(threading.Thread):
|
|
148
150
|
except queue.Empty:
|
149
151
|
return None
|
150
152
|
|
151
|
-
def get_all_changes(self, timeout: float =
|
153
|
+
def get_all_changes(self, timeout: float = _WATCHER_TIMEOUT) -> list[str]:
|
152
154
|
"""Get all file change events from the queue
|
153
155
|
|
154
156
|
Args:
|
@@ -185,12 +187,22 @@ def _process_wrapper(root: Path, excluded_patterns: list[str], queue: Queue):
|
|
185
187
|
watcher.stop()
|
186
188
|
|
187
189
|
|
190
|
+
class ProcessWraperTask:
|
191
|
+
def __init__(self, root: Path, excluded_patterns: list[str], queue: Queue) -> None:
|
192
|
+
self.root = root
|
193
|
+
self.excluded_patterns = excluded_patterns
|
194
|
+
self.queue = queue
|
195
|
+
|
196
|
+
def run(self):
|
197
|
+
_process_wrapper(self.root, self.excluded_patterns, self.queue)
|
198
|
+
|
199
|
+
|
188
200
|
class FileWatcherProcess:
|
189
201
|
def __init__(self, root: Path, excluded_patterns: list[str]) -> None:
|
190
202
|
self.queue: Queue = Queue()
|
203
|
+
task = ProcessWraperTask(root, excluded_patterns, self.queue)
|
191
204
|
self.process = Process(
|
192
|
-
target=
|
193
|
-
args=(root, excluded_patterns, self.queue),
|
205
|
+
target=task.run,
|
194
206
|
daemon=True,
|
195
207
|
)
|
196
208
|
self.process.start()
|
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()
|