Skip to content

Commit 15622f2

Browse files
authored
Initial CLI setup (#1331)
1 parent ab6ea72 commit 15622f2

10 files changed

Lines changed: 388 additions & 51 deletions

File tree

.devcontainer/devcontainer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,5 @@
5353
},
5454
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
5555
"name": "Asynchronous Python client for WLED",
56-
"updateContentCommand": ". ${NVM_DIR}/nvm.sh && nvm install && nvm use && npm install && poetry install && poetry run pre-commit install"
56+
"updateContentCommand": ". ${NVM_DIR}/nvm.sh && nvm install && nvm use && npm install && poetry install --extras cli && poetry run pre-commit install"
5757
}

.github/workflows/linting.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
poetry config virtualenvs.create true
3131
poetry config virtualenvs.in-project true
3232
- name: 🏗 Install Python dependencies
33-
run: poetry install --no-interaction
33+
run: poetry install --extras cli --no-interaction
3434
- name: 🚀 Check code for common misspellings
3535
run: poetry run pre-commit run codespell --all-files
3636

@@ -53,7 +53,7 @@ jobs:
5353
poetry config virtualenvs.create true
5454
poetry config virtualenvs.in-project true
5555
- name: 🏗 Install Python dependencies
56-
run: poetry install --no-interaction
56+
run: poetry install --extras cli --no-interaction
5757
- name: 🚀 Run ruff linter
5858
run: poetry run ruff check --output-format=github .
5959
- name: 🚀 Run ruff formatter
@@ -78,7 +78,7 @@ jobs:
7878
poetry config virtualenvs.create true
7979
poetry config virtualenvs.in-project true
8080
- name: 🏗 Install Python dependencies
81-
run: poetry install --no-interaction
81+
run: poetry install --extras cli --no-interaction
8282
- name: 🚀 Check Python AST
8383
run: poetry run pre-commit run check-ast --all-files
8484
- name: 🚀 Check for case conflicts
@@ -123,7 +123,7 @@ jobs:
123123
poetry config virtualenvs.create true
124124
poetry config virtualenvs.in-project true
125125
- name: 🏗 Install Python dependencies
126-
run: poetry install --no-interaction
126+
run: poetry install --extras cli --no-interaction
127127
- name: 🚀 Run pylint
128128
run: poetry run pre-commit run pylint --all-files
129129

@@ -146,7 +146,7 @@ jobs:
146146
poetry config virtualenvs.create true
147147
poetry config virtualenvs.in-project true
148148
- name: 🏗 Install Python dependencies
149-
run: poetry install --no-interaction
149+
run: poetry install --extras cli --no-interaction
150150
- name: 🚀 Run yamllint
151151
run: poetry run yamllint .
152152

@@ -169,7 +169,7 @@ jobs:
169169
poetry config virtualenvs.create true
170170
poetry config virtualenvs.in-project true
171171
- name: 🏗 Install Python dependencies
172-
run: poetry install --no-interaction
172+
run: poetry install --extras cli --no-interaction
173173
- name: 🏗 Set up Node.js
174174
uses: actions/setup-node@v4.0.2
175175
with:

.github/workflows/release.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
poetry config virtualenvs.create true
3737
poetry config virtualenvs.in-project true
3838
- name: 🏗 Install dependencies
39-
run: poetry install --no-interaction
39+
run: poetry install --extras cli --no-interaction
4040
- name: 🏗 Set package version
4141
run: |
4242
version="${{ github.event.release.tag_name }}"

.github/workflows/tests.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
poetry config virtualenvs.create true
3434
poetry config virtualenvs.in-project true
3535
- name: 🏗 Install dependencies
36-
run: poetry install --no-interaction
36+
run: poetry install --extras cli --no-interaction
3737
- name: 🚀 Run pytest
3838
run: poetry run pytest --cov wled tests
3939
- name: ⬆️ Upload coverage artifact
@@ -65,7 +65,7 @@ jobs:
6565
poetry config virtualenvs.create true
6666
poetry config virtualenvs.in-project true
6767
- name: 🏗 Install dependencies
68-
run: poetry install --no-interaction
68+
run: poetry install --extras cli --no-interaction
6969
- name: 🚀 Process coverage results
7070
run: |
7171
poetry run coverage combine coverage*/.coverage*

.github/workflows/typing.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ jobs:
3030
poetry config virtualenvs.create true
3131
poetry config virtualenvs.in-project true
3232
- name: 🏗 Install dependencies
33-
run: poetry install --no-interaction
33+
run: poetry install --extras cli --no-interaction
3434
- name: 🚀 Run mypy
3535
run: poetry run mypy examples src tests

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ To install all packages, including all development requirements:
9595

9696
```bash
9797
npm install
98-
poetry install
98+
poetry install --extras cli
9999
```
100100

101101
As this repository uses the [pre-commit][pre-commit] framework, all changes

poetry.lock

Lines changed: 91 additions & 39 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ backoff = ">=2.2.0"
3131
cachetools = ">=4.0.0"
3232
python = "^3.11"
3333
yarl = ">=1.6.0"
34+
typer = {version = "^0.12.3", optional = true, extras = ["all"]}
35+
zeroconf = {version = "^0.132.2", optional = true, extras = ["all"]}
36+
37+
[tool.poetry.extras]
38+
cli = ["typer", "zeroconf"]
39+
40+
[tool.poetry.scripts]
41+
wled = "wled.cli:cli"
3442

3543
[tool.poetry.urls]
3644
"Bug Tracker" = "https://github.com/frenck/python-wled/issues"
@@ -60,6 +68,7 @@ source = ["wled"]
6068
[tool.coverage.report]
6169
fail_under = 53
6270
show_missing = true
71+
omit = ["src/wled/cli/*"]
6372

6473
[tool.mypy]
6574
# Specify the target platform details in config, so your developers are

src/wled/cli/__init__.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""Asynchronous Python client for WLED."""
2+
3+
import asyncio
4+
5+
from rich.console import Console
6+
from rich.live import Live
7+
from rich.table import Table
8+
from zeroconf import ServiceStateChange, Zeroconf
9+
from zeroconf.asyncio import AsyncServiceBrowser, AsyncServiceInfo, AsyncZeroconf
10+
11+
from .async_typer import AsyncTyper
12+
13+
cli = AsyncTyper(help="WLED CLI", no_args_is_help=True, add_completion=False)
14+
console = Console()
15+
16+
17+
@cli.command("scan")
18+
async def test() -> None:
19+
"""Scan for WLED devices on the network."""
20+
zeroconf = AsyncZeroconf()
21+
background_tasks = set()
22+
23+
table = Table(
24+
title="\n\nFound WLED devices", header_style="cyan bold", show_lines=True
25+
)
26+
table.add_column("Addresses")
27+
table.add_column("MAC Address")
28+
29+
def async_on_service_state_change(
30+
zeroconf: Zeroconf,
31+
service_type: str,
32+
name: str,
33+
state_change: ServiceStateChange,
34+
) -> None:
35+
"""Handle service state changes."""
36+
if state_change is not ServiceStateChange.Added:
37+
return
38+
39+
future = asyncio.ensure_future(
40+
async_display_service_info(zeroconf, service_type, name)
41+
)
42+
background_tasks.add(future)
43+
future.add_done_callback(background_tasks.discard)
44+
45+
async def async_display_service_info(
46+
zeroconf: Zeroconf, service_type: str, name: str
47+
) -> None:
48+
"""Retrieve and display service info."""
49+
info = AsyncServiceInfo(service_type, name)
50+
await info.async_request(zeroconf, 3000)
51+
if info is None:
52+
return
53+
54+
console.print(f"[cyan bold]Found service {info.server}: is a WLED device 🎉")
55+
56+
table.add_row(
57+
f"{str(info.server).rstrip('.')}\n"
58+
+ ", ".join(info.parsed_scoped_addresses()),
59+
info.properties[b"mac"].decode(), # type: ignore[union-attr]
60+
)
61+
62+
console.print("[green]Scanning for WLED devices...")
63+
console.print("[red]Press Ctrl-C to exit\n")
64+
65+
with Live(table, console=console, refresh_per_second=4):
66+
browser = AsyncServiceBrowser(
67+
zeroconf.zeroconf,
68+
"_wled._tcp.local.",
69+
handlers=[async_on_service_state_change],
70+
)
71+
72+
try:
73+
while True:
74+
await asyncio.sleep(0.5)
75+
except KeyboardInterrupt:
76+
pass
77+
finally:
78+
console.print("\n[green]Control-C pressed, stopping scan")
79+
await browser.async_cancel()
80+
await zeroconf.async_close()
81+
82+
83+
if __name__ == "__main__":
84+
cli()

0 commit comments

Comments
 (0)