Skip to content

Commit 4a1e302

Browse files
committed
Refactor __main__.py to use Typer
1 parent 4f1995c commit 4a1e302

File tree

2 files changed

+31
-81
lines changed

2 files changed

+31
-81
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"pydantic_core >= 2.23.4",
6363
"tomli >= 2.0.2",
6464
"websockets >= 13.1",
65+
"typer >= 0.15.1",
6566
]
6667

6768
[project.optional-dependencies]

streamdeck/__main__.py

Lines changed: 30 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
from __future__ import annotations
2-
31
import json
42
import logging
53
import sys
6-
from argparse import ArgumentParser
74
from pathlib import Path
8-
from typing import Protocol, cast
5+
from typing import Annotated, Union
6+
7+
import typer
98

109
from streamdeck.manager import PluginManager
1110
from streamdeck.models.configs import PyProjectConfigs
@@ -16,99 +15,45 @@
1615

1716

1817

19-
class DirectoryNotFoundError(FileNotFoundError):
20-
"""Custom exception to indicate that a specified directory was not found."""
21-
def __init__(self, *args: object, directory: Path):
22-
super().__init__(*args)
23-
self.directory = directory
24-
25-
26-
class CliArgsNamespace(Protocol):
27-
"""Represents the command-line arguments namespace."""
28-
plugin_dir: Path | None
29-
action_scripts: list[str] | None
30-
31-
# Args always passed in by StreamDeck software
32-
port: int
33-
pluginUUID: str # noqa: N815
34-
registerEvent: str # noqa: N815
35-
info: str # Actually a string representation of json object
18+
plugin = typer.Typer()
3619

3720

38-
def setup_cli() -> ArgumentParser:
39-
"""Set up the command-line interface for the script.
21+
@plugin.command()
22+
def main(
23+
port: Annotated[int, typer.Option("-p", "-port")],
24+
plugin_registration_uuid: Annotated[str, typer.Option("-pluginUUID")],
25+
register_event: Annotated[str, typer.Option("-registerEvent")],
26+
info: Annotated[str, typer.Option("-info")],
27+
plugin_dir: Annotated[Path, typer.Option(file_okay=False, exists=True, readable=True)] = Path.cwd(), # noqa: B008
28+
action_scripts: Union[list[str], None] = None, # noqa: UP007
29+
) -> None:
30+
"""Start the Stream Deck plugin with the given configuration.
4031
41-
Returns:
42-
argparse.ArgumentParser: The argument parser for the CLI.
32+
NOTE: Single flag long-name options are extected & passed in by the Stream Deck software.
33+
Double flag long-name options are used during development and testing.
4334
"""
44-
parser = ArgumentParser(description="CLI to load Actions from action scripts.")
45-
group = parser.add_mutually_exclusive_group(required=False)
46-
group.add_argument(
47-
"plugin_dir",
48-
type=Path,
49-
nargs="?",
50-
help="The directory containing plugin files to load Actions from.",
51-
)
52-
group.add_argument(
53-
"--action-scripts",
54-
type=str,
55-
nargs="+",
56-
help="A list of action script file paths to load Actions from or a single value to be processed.",
57-
)
58-
59-
# Options that will always be passed in by the StreamDeck software when running this plugin.
60-
parser.add_argument("-port", dest="port", type=int, help="Port", required=True)
61-
parser.add_argument(
62-
"-pluginUUID", dest="pluginUUID", type=str, help="pluginUUID", required=True
63-
)
64-
parser.add_argument(
65-
"-registerEvent", dest="registerEvent", type=str, help="registerEvent", required=True
66-
)
67-
parser.add_argument("-info", dest="info", type=str, help="info", required=True)
68-
69-
return parser
70-
71-
72-
def main() -> None:
73-
"""Main function to parse arguments, load actions, and execute them."""
74-
parser = setup_cli()
75-
args = cast(CliArgsNamespace, parser.parse_args())
76-
77-
# If `plugin_dir` was not passed in as a cli option, then fall back to using the CWD.
78-
if args.plugin_dir is None:
79-
plugin_dir = Path.cwd()
80-
# Also validate the plugin_dir argument.
81-
elif not args.plugin_dir.is_dir():
82-
msg = f"The provided plugin directory '{args.plugin_dir}' is not a directory."
83-
raise NotADirectoryError(msg)
84-
elif not args.plugin_dir.exists():
85-
msg = f"The provided plugin directory '{args.plugin_dir}' does not exist."
86-
raise DirectoryNotFoundError(msg, directory=args.plugin_dir)
87-
else:
88-
plugin_dir = args.plugin_dir
89-
9035
# Ensure plugin_dir is in `sys.path`, so that import statements in the plugin module will work as expected.
9136
if str(plugin_dir) not in sys.path:
9237
sys.path.insert(0, str(plugin_dir))
9338

94-
info = json.loads(args.info)
95-
plugin_uuid = info["plugin"]["uuid"]
39+
info_data = json.loads(info)
40+
plugin_uuid = info_data["plugin"]["uuid"]
9641

9742
# After configuring once here, we can grab the logger in any other module with `logging.getLogger("streamdeck")`, or
9843
# a child logger with `logging.getLogger("streamdeck.mycomponent")`, all with the same handler/formatter configuration.
9944
configure_streamdeck_logger(name="streamdeck", plugin_uuid=plugin_uuid)
10045

101-
pyproject = PyProjectConfigs.validate_from_toml_file(plugin_dir / "pyproject.toml")
102-
actions = list(pyproject.streamdeck_plugin_actions)
46+
pyproject = PyProjectConfigs.validate_from_toml_file(plugin_dir / "pyproject.toml", action_scripts=action_scripts)
47+
actions = pyproject.streamdeck_plugin_actions
10348

10449
manager = PluginManager(
105-
port=args.port,
50+
port=port,
10651
plugin_uuid=plugin_uuid,
10752
# NOT the configured plugin UUID in the manifest.json,
10853
# which can be pulled out of `info["plugin"]["uuid"]`
109-
plugin_registration_uuid=args.pluginUUID,
110-
register_event=args.registerEvent,
111-
info=info,
54+
plugin_registration_uuid=plugin_registration_uuid,
55+
register_event=register_event,
56+
info=info_data,
11257
)
11358

11459
for action in actions:
@@ -117,5 +62,9 @@ def main() -> None:
11762
manager.run()
11863

11964

120-
if __name__ == "__main__":
121-
main()
65+
# Also run the plugin if this script is ran as a console script.
66+
if __name__ in ("__main__", "streamdeck.__main__"):
67+
plugin()
68+
69+
70+

0 commit comments

Comments
 (0)