fastled 1.2.48__py3-none-any.whl → 1.2.50__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/client_server.py +405 -405
- fastled/docker_manager.py +49 -17
- fastled/keyboard.py +1 -0
- fastled/project_init.py +129 -129
- fastled/site/build.py +457 -457
- fastled/web_compile.py +2 -1
- {fastled-1.2.48.dist-info → fastled-1.2.50.dist-info}/METADATA +384 -383
- {fastled-1.2.48.dist-info → fastled-1.2.50.dist-info}/RECORD +13 -13
- {fastled-1.2.48.dist-info → fastled-1.2.50.dist-info}/WHEEL +1 -1
- {fastled-1.2.48.dist-info → fastled-1.2.50.dist-info}/entry_points.txt +0 -0
- {fastled-1.2.48.dist-info → fastled-1.2.50.dist-info/licenses}/LICENSE +0 -0
- {fastled-1.2.48.dist-info → fastled-1.2.50.dist-info}/top_level.txt +0 -0
fastled/docker_manager.py
CHANGED
@@ -18,7 +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
|
+
from docker.errors import DockerException, ImageNotFound, NotFound
|
22
22
|
from docker.models.containers import Container
|
23
23
|
from docker.models.images import Image
|
24
24
|
from filelock import FileLock
|
@@ -98,6 +98,11 @@ class RunningContainer:
|
|
98
98
|
self.running = False
|
99
99
|
self.thread.join()
|
100
100
|
|
101
|
+
def stop(self) -> None:
|
102
|
+
"""Stop the container"""
|
103
|
+
self.container.stop()
|
104
|
+
self.detach()
|
105
|
+
|
101
106
|
|
102
107
|
class DockerManager:
|
103
108
|
def __init__(self) -> None:
|
@@ -316,11 +321,12 @@ class DockerManager:
|
|
316
321
|
subprocess.run(cmd_list, check=True)
|
317
322
|
print(f"Updated to newer version of {image_name}:{tag}")
|
318
323
|
local_image_hash = self.client.images.get(f"{image_name}:{tag}").id
|
324
|
+
assert local_image_hash is not None
|
319
325
|
if remote_image_hash is not None:
|
320
326
|
DISK_CACHE.put(local_image_hash, remote_image_hash)
|
321
327
|
return True
|
322
328
|
|
323
|
-
except
|
329
|
+
except ImageNotFound:
|
324
330
|
print(f"Image {image_name}:{tag} not found.")
|
325
331
|
with Spinner("Loading "):
|
326
332
|
# We use docker cli here because it shows the download.
|
@@ -332,7 +338,7 @@ class DockerManager:
|
|
332
338
|
local_image = self.client.images.get(f"{image_name}:{tag}")
|
333
339
|
local_image_hash = local_image.id
|
334
340
|
print(f"Image {image_name}:{tag} downloaded successfully.")
|
335
|
-
except
|
341
|
+
except ImageNotFound:
|
336
342
|
warnings.warn(f"Image {image_name}:{tag} not found after download.")
|
337
343
|
return True
|
338
344
|
|
@@ -354,8 +360,11 @@ class DockerManager:
|
|
354
360
|
"""Compare if existing container has matching configuration"""
|
355
361
|
try:
|
356
362
|
# Check if container is using the same image
|
357
|
-
|
358
|
-
|
363
|
+
image = container.image
|
364
|
+
assert image is not None
|
365
|
+
container_image_id = image.id
|
366
|
+
container_image_tags = image.tags
|
367
|
+
assert container_image_id is not None
|
359
368
|
|
360
369
|
# Simplified image comparison - just compare the IDs directly
|
361
370
|
if not container_image_tags:
|
@@ -421,7 +430,7 @@ class DockerManager:
|
|
421
430
|
return False
|
422
431
|
except KeyboardInterrupt:
|
423
432
|
raise
|
424
|
-
except
|
433
|
+
except NotFound:
|
425
434
|
print("Container not found.")
|
426
435
|
return False
|
427
436
|
except Exception as e:
|
@@ -502,13 +511,13 @@ class DockerManager:
|
|
502
511
|
print(out_msg)
|
503
512
|
print("#" * msg_len + "\n")
|
504
513
|
container = self.client.containers.run(
|
505
|
-
image_name,
|
506
|
-
command,
|
514
|
+
image=image_name,
|
515
|
+
command=command,
|
507
516
|
name=container_name,
|
508
517
|
detach=True,
|
509
518
|
tty=True,
|
510
519
|
volumes=volumes,
|
511
|
-
ports=ports,
|
520
|
+
ports=ports, # type: ignore
|
512
521
|
remove=True,
|
513
522
|
)
|
514
523
|
return container
|
@@ -563,7 +572,10 @@ class DockerManager:
|
|
563
572
|
Returns a RunningContainer object that can be used to stop monitoring.
|
564
573
|
"""
|
565
574
|
if isinstance(container, str):
|
566
|
-
|
575
|
+
container_name = container
|
576
|
+
tmp = self.get_container(container)
|
577
|
+
assert tmp is not None, f"Container {container_name} not found."
|
578
|
+
container = tmp
|
567
579
|
|
568
580
|
assert container is not None, "Container not found."
|
569
581
|
|
@@ -580,10 +592,13 @@ class DockerManager:
|
|
580
592
|
"""
|
581
593
|
if isinstance(container, str):
|
582
594
|
container_name = container
|
583
|
-
container = self.get_container(container)
|
584
|
-
|
595
|
+
# container = self.get_container(container)
|
596
|
+
tmp = self.get_container(container_name)
|
597
|
+
if not tmp:
|
585
598
|
print(f"Could not put container {container_name} to sleep.")
|
586
599
|
return
|
600
|
+
container = tmp
|
601
|
+
assert isinstance(container, Container)
|
587
602
|
try:
|
588
603
|
if platform.system() == "Windows":
|
589
604
|
container.pause()
|
@@ -600,16 +615,27 @@ class DockerManager:
|
|
600
615
|
"""
|
601
616
|
Resume (unpause) the container.
|
602
617
|
"""
|
618
|
+
container_name = "UNKNOWN"
|
603
619
|
if isinstance(container, str):
|
604
|
-
|
620
|
+
container_name = container
|
621
|
+
container_or_none = self.get_container(container)
|
622
|
+
if container_or_none is None:
|
623
|
+
print(f"Could not resume container {container}.")
|
624
|
+
return
|
625
|
+
container = container_or_none
|
626
|
+
container_name = container.name
|
627
|
+
elif isinstance(container, Container):
|
628
|
+
container_name = container.name
|
629
|
+
assert isinstance(container, Container)
|
605
630
|
if not container:
|
606
631
|
print(f"Could not resume container {container}.")
|
607
632
|
return
|
608
633
|
try:
|
634
|
+
assert isinstance(container, Container)
|
609
635
|
container.unpause()
|
610
636
|
print(f"Container {container.name} has been resumed.")
|
611
637
|
except Exception as e:
|
612
|
-
print(f"Failed to resume container {
|
638
|
+
print(f"Failed to resume container {container_name}: {e}")
|
613
639
|
|
614
640
|
def get_container(self, container_name: str) -> Container | None:
|
615
641
|
"""
|
@@ -753,6 +779,7 @@ def main():
|
|
753
779
|
# new_tag = "my-python"
|
754
780
|
container_name = "my-python-container"
|
755
781
|
command = "python -m http.server"
|
782
|
+
running_container: RunningContainer | None = None
|
756
783
|
|
757
784
|
try:
|
758
785
|
# Step 1: Validate or download the image
|
@@ -775,13 +802,18 @@ def main():
|
|
775
802
|
|
776
803
|
except KeyboardInterrupt:
|
777
804
|
print("\nStopping container...")
|
778
|
-
running_container
|
779
|
-
|
780
|
-
docker_manager.
|
805
|
+
if isinstance(running_container, RunningContainer):
|
806
|
+
running_container.stop()
|
807
|
+
container_or_none = docker_manager.get_container(container_name)
|
808
|
+
if container_or_none is not None:
|
809
|
+
docker_manager.suspend_container(container_or_none)
|
810
|
+
else:
|
811
|
+
warnings.warn(f"Container {container_name} not found.")
|
781
812
|
|
782
813
|
try:
|
783
814
|
# Suspend and resume the container
|
784
815
|
container = docker_manager.get_container(container_name)
|
816
|
+
assert container is not None, "Container not found."
|
785
817
|
docker_manager.suspend_container(container)
|
786
818
|
|
787
819
|
input("Press Enter to resume the container...")
|
fastled/keyboard.py
CHANGED
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()
|