From a396a0c5511932aa111923649ed0e5d676d7ea06 Mon Sep 17 00:00:00 2001 From: noahhusby <32528627+noahhusby@users.noreply.github.com> Date: Fri, 25 Oct 2024 11:25:48 -0400 Subject: [PATCH] Implement proper type checking --- .pre-commit-config.yaml | 8 +- aiostreammagic/models.py | 227 +++++++++++++++++++-------------- aiostreammagic/stream_magic.py | 66 +++++----- examples/basic.py | 2 +- examples/subscribe.py | 6 +- poetry.lock | 65 +++++++++- pyproject.toml | 33 ++++- 7 files changed, 270 insertions(+), 137 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7a6d6d1..cb80b34 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,9 +16,9 @@ repos: entry: poetry run ruff format require_serial: true stages: [commit, push, manual] - - id: pytest - name: 🧪 Running tests and test coverage with pytest + - id: mypy + name: Static type checking using mypy language: system types: [python] - entry: poetry run pytest - pass_filenames: false \ No newline at end of file + entry: poetry run mypy + require_serial: true diff --git a/aiostreammagic/models.py b/aiostreammagic/models.py index 568a68d..c98c37d 100644 --- a/aiostreammagic/models.py +++ b/aiostreammagic/models.py @@ -4,11 +4,65 @@ from dataclasses import dataclass, field from enum import StrEnum +from typing import Optional from mashumaro import field_options from mashumaro.mixins.orjson import DataClassORJSONMixin +class TransportControl(StrEnum): + """Control enum.""" + + PAUSE = "pause" + PLAY = "play" + PLAY_PAUSE = "play_pause" + TOGGLE_SHUFFLE = "toggle_shuffle" + TOGGLE_REPEAT = "toggle_repeat" + TRACK_NEXT = "track_next" + TRACK_PREVIOUS = "track_previous" + SEEK = "seek" + STOP = "stop" + + +class ShuffleMode(StrEnum): + """Shuffle mode.""" + + OFF = "off" + ALL = "all" + TOGGLE = "toggle" + + +class RepeatMode(StrEnum): + """Repeat mode.""" + + OFF = "off" + ALL = "all" + TOGGLE = "toggle" + + +class CallbackType(StrEnum): + """Callback type.""" + + STATE = "state" + CONNECTION = "connection" + + +class DisplayBrightness(StrEnum): + """Display brightness.""" + + BRIGHT = "bright" + DIM = "dim" + OFF = "off" + + +class ControlBusMode(StrEnum): + """Control bus mode.""" + + AMPLIFIER = "amplifier" + RECEIVER = "receiver" + OFF = "off" + + @dataclass class Info(DataClassORJSONMixin): """Cambridge Audio device metadata.""" @@ -33,7 +87,9 @@ class Source(DataClassORJSONMixin): ui_selectable: bool = field(metadata=field_options(alias="ui_selectable")) description: str = field(metadata=field_options(alias="description")) description_locale: str = field(metadata=field_options(alias="description_locale")) - preferred_order: int = field(metadata=field_options(alias="preferred_order"), default=None) + preferred_order: Optional[int] = field( + metadata=field_options(alias="preferred_order"), default=None + ) @dataclass @@ -44,64 +100,88 @@ class State(DataClassORJSONMixin): power: bool = field(metadata=field_options(alias="power")) pre_amp_mode: bool = field(metadata=field_options(alias="pre_amp_mode")) pre_amp_state: bool = field(metadata=field_options(alias="pre_amp_state")) - volume_step: int = field(metadata=field_options(alias="volume_step"), default=None) - volume_db: int = field(metadata=field_options(alias="volume_db"), default=None) - volume_percent: int = field( + volume_step: Optional[int] = field( + metadata=field_options(alias="volume_step"), default=None + ) + volume_db: Optional[int] = field( + metadata=field_options(alias="volume_db"), default=None + ) + volume_percent: Optional[int] = field( metadata=field_options(alias="volume_percent"), default=None ) mute: bool = field(metadata=field_options(alias="mute"), default=False) - audio_output: str = field( + audio_output: Optional[str] = field( metadata=field_options(alias="audio_output"), default=None ) control_bus: ControlBusMode = field( - metadata=field_options(alias="cbus"), default="off" + metadata=field_options(alias="cbus"), default=ControlBusMode.OFF ) +@dataclass +class PlayStateMetadata(DataClassORJSONMixin): + """Data class representing StreamMagic play state metadata.""" + + class_name: Optional[str] = field( + metadata=field_options(alias="class"), default=None + ) + source: Optional[str] = field(metadata=field_options(alias="source"), default=None) + name: Optional[str] = field(metadata=field_options(alias="name"), default=None) + title: Optional[str] = field(metadata=field_options(alias="title"), default=None) + art_url: Optional[str] = field( + metadata=field_options(alias="art_url"), default=None + ) + sample_format: Optional[str] = field( + metadata=field_options(alias="sample_format"), default=None + ) + mqa: Optional[str] = field(metadata=field_options(alias="mqa"), default=None) + signal: Optional[bool] = field(metadata=field_options(alias="signal"), default=None) + codec: Optional[str] = field(metadata=field_options(alias="codec"), default=None) + lossless: Optional[bool] = field( + metadata=field_options(alias="lossless"), default=None + ) + sample_rate: Optional[int] = field( + metadata=field_options(alias="sample_rate"), default=None + ) + bitrate: Optional[int] = field( + metadata=field_options(alias="bitrate"), default=None + ) + encoding: Optional[str] = field( + metadata=field_options(alias="encoding"), default=None + ) + radio_id: Optional[int] = field( + metadata=field_options(alias="radio_id"), default=None + ) + duration: Optional[int] = field( + metadata=field_options(alias="duration"), default=None + ) + artist: Optional[str] = field(metadata=field_options(alias="artist"), default=None) + station: Optional[str] = field( + metadata=field_options(alias="station"), default=None + ) + album: Optional[str] = field(metadata=field_options(alias="album"), default=None) + + @dataclass class PlayState(DataClassORJSONMixin): """Data class representing StreamMagic play state.""" state: str = field(metadata=field_options(alias="state"), default="not_ready") metadata: PlayStateMetadata = field( - metadata=field_options(alias="metadata"), default=None + metadata=field_options(alias="metadata"), default_factory=PlayStateMetadata ) presettable: bool = field( metadata=field_options(alias="presettable"), default=False ) - position: int = field(metadata=field_options(alias="position"), default=None) + position: Optional[int] = field( + metadata=field_options(alias="position"), default=None + ) mode_repeat: str = field(metadata=field_options(alias="mode_repeat"), default="off") mode_shuffle: str = field( metadata=field_options(alias="mode_shuffle"), default="off" ) -@dataclass -class PlayStateMetadata(DataClassORJSONMixin): - """Data class representing StreamMagic play state metadata.""" - - class_name: str = field(metadata=field_options(alias="class"), default=None) - source: str = field(metadata=field_options(alias="source"), default=None) - name: str = field(metadata=field_options(alias="name"), default=None) - title: str = field(metadata=field_options(alias="title"), default=None) - art_url: str = field(metadata=field_options(alias="art_url"), default=None) - sample_format: str = field( - metadata=field_options(alias="sample_format"), default=None - ) - mqa: str = field(metadata=field_options(alias="mqa"), default=None) - signal: bool = field(metadata=field_options(alias="signal"), default=None) - codec: str = field(metadata=field_options(alias="codec"), default=None) - lossless: bool = field(metadata=field_options(alias="lossless"), default=None) - sample_rate: int = field(metadata=field_options(alias="sample_rate"), default=None) - bitrate: int = field(metadata=field_options(alias="bitrate"), default=None) - encoding: str = field(metadata=field_options(alias="encoding"), default=None) - radio_id: int | None = field(metadata=field_options(alias="radio_id"), default=None) - duration: int | None = field(metadata=field_options(alias="duration"), default=None) - artist: str | None = field(metadata=field_options(alias="artist"), default=None) - station: str | None = field(metadata=field_options(alias="station"), default=None) - album: str | None = field(metadata=field_options(alias="album"), default=None) - - @dataclass class PresetList(DataClassORJSONMixin): """Data class representing StreamMagic preset table.""" @@ -112,21 +192,25 @@ class PresetList(DataClassORJSONMixin): presettable: bool = field( metadata=field_options(alias="presettable"), default=False ) - presets: list[Preset] = field(metadata=field_options(alias="presets"), default=None) + presets: list[Preset] = field( + metadata=field_options(alias="presets"), default_factory=list + ) @dataclass class Preset(DataClassORJSONMixin): """Data class representing StreamMagic preset.""" - preset_id: int = field(metadata=field_options(alias="id"), default=None) - name: str = field(metadata=field_options(alias="name"), default=None) - type: str = field(metadata=field_options(alias="type"), default=None) - preset_class: str = field(metadata=field_options(alias="class"), default=None) - state: str = field(metadata=field_options(alias="state"), default=None) + preset_id: int = field(metadata=field_options(alias="id")) + name: str = field(metadata=field_options(alias="name")) + type: str = field(metadata=field_options(alias="type")) + preset_class: str = field(metadata=field_options(alias="class")) + state: str = field(metadata=field_options(alias="state")) is_playing: bool = field(metadata=field_options(alias="is_playing"), default=False) - art_url: str = field(metadata=field_options(alias="art_url"), default=None) - airable_radio_id: int = field( + art_url: Optional[str] = field( + metadata=field_options(alias="art_url"), default=None + ) + airable_radio_id: Optional[int] = field( metadata=field_options(alias="airable_radio_id"), default=None ) @@ -136,7 +220,7 @@ class NowPlaying(DataClassORJSONMixin): """Data class representing NowPlaying state.""" controls: list[TransportControl] = field( - metadata=field_options(alias="controls"), default=None + metadata=field_options(alias="controls"), default_factory=list ) @@ -144,7 +228,9 @@ class NowPlaying(DataClassORJSONMixin): class AudioOutput(DataClassORJSONMixin): """Data class representing StreamMagic audio output.""" - outputs: list[Output] = field(metadata=field_options(alias="outputs"), default=None) + outputs: list[Output] = field( + metadata=field_options(alias="outputs"), default_factory=list + ) @dataclass @@ -169,56 +255,3 @@ class Update(DataClassORJSONMixin): metadata=field_options(alias="update_available"), default=False ) updating: bool = field(metadata=field_options(alias="updating"), default=False) - - -class TransportControl(StrEnum): - """Control enum.""" - - PAUSE = "pause" - PLAY = "play" - PLAY_PAUSE = "play_pause" - TOGGLE_SHUFFLE = "toggle_shuffle" - TOGGLE_REPEAT = "toggle_repeat" - TRACK_NEXT = "track_next" - TRACK_PREVIOUS = "track_previous" - SEEK = "seek" - STOP = "stop" - - -class ShuffleMode(StrEnum): - """Shuffle mode.""" - - OFF = "off" - ALL = "all" - TOGGLE = "toggle" - - -class RepeatMode(StrEnum): - """Repeat mode.""" - - OFF = "off" - ALL = "all" - TOGGLE = "toggle" - - -class CallbackType(StrEnum): - """Callback type.""" - - STATE = "state" - CONNECTION = "connection" - - -class DisplayBrightness(StrEnum): - """Display brightness.""" - - BRIGHT = "bright" - DIM = "dim" - OFF = "off" - - -class ControlBusMode(StrEnum): - """Control bus mode.""" - - AMPLIFIER = "amplifier" - RECEIVER = "receiver" - OFF = "off" diff --git a/aiostreammagic/stream_magic.py b/aiostreammagic/stream_magic.py index e3ae186..c66b4ab 100644 --- a/aiostreammagic/stream_magic.py +++ b/aiostreammagic/stream_magic.py @@ -2,7 +2,7 @@ import asyncio import json -from asyncio import AbstractEventLoop, Future, Task +from asyncio import AbstractEventLoop, Future, Task, Queue from datetime import datetime, UTC from typing import Any, Optional @@ -35,14 +35,14 @@ class StreamMagicClient: """Client for handling connections with StreamMagic enabled devices.""" - def __init__(self, host): + def __init__(self, host: str) -> None: self.host = host self.connection: WebSocketClientProtocol | None = None - self.futures: dict[str, list[asyncio.Future]] = {} + self.futures: dict[str, list[Future[Any]]] = {} self._subscriptions: dict[str, Any] = {} self._loop: AbstractEventLoop = asyncio.get_running_loop() - self.connect_result: Future | None = None - self.connect_task: Task | None = None + self.connect_result: Future[bool] | None = None + self.connect_task: Task[Any] | None = None self.state_update_callbacks: list[Any] = [] self._allow_state_update = False self._info: Optional[Info] = None @@ -55,27 +55,27 @@ def __init__(self, host): self._update: Optional[Update] = None self._preset_list: Optional[PresetList] = None self._attempt_reconnection = False - self._reconnect_task: Optional[Task] = None + self._reconnect_task: Optional[Task[Any]] = None self.position_last_updated: datetime = datetime.now() - async def register_state_update_callbacks(self, callback: Any): + async def register_state_update_callbacks(self, callback: Any) -> None: """Register state update callback.""" self.state_update_callbacks.append(callback) if self._allow_state_update: await callback(self, CallbackType.STATE) - def unregister_state_update_callbacks(self, callback: Any): + def unregister_state_update_callbacks(self, callback: Any) -> None: """Unregister state update callback.""" if callback in self.state_update_callbacks: self.state_update_callbacks.remove(callback) - def clear_state_update_callbacks(self): + def clear_state_update_callbacks(self) -> None: """Clear state update callbacks.""" self.state_update_callbacks.clear() async def do_state_update_callbacks( self, callback_type: CallbackType = CallbackType.STATE - ): + ) -> None: """Call state update callbacks.""" if not self.state_update_callbacks: return @@ -86,7 +86,7 @@ async def do_state_update_callbacks( if callbacks: await asyncio.gather(*callbacks) - async def connect(self): + async def connect(self) -> Any: """Connect to StreamMagic enabled devices.""" if not self.is_connected(): self.connect_result = self._loop.create_future() @@ -95,7 +95,7 @@ async def connect(self): ) return await self.connect_result - async def disconnect(self): + async def disconnect(self) -> None: """Disconnect from StreamMagic enabled devices.""" if self.is_connected(): self._attempt_reconnection = False @@ -110,14 +110,14 @@ def is_connected(self) -> bool: """Return True if device is connected.""" return self.connect_task is not None and not self.connect_task.done() - async def _ws_connect(self, uri): + async def _ws_connect(self, uri: str) -> WebSocketClientProtocol: """Establish a connection with a WebSocket.""" return await ws_connect( uri, extra_headers={"Origin": f"ws://{self.host}", "Host": f"{self.host}:80"}, ) - async def _reconnect_handler(self, res): + async def _reconnect_handler(self, res: Future[bool]) -> None: reconnect_delay = 0.5 while True: try: @@ -138,7 +138,7 @@ async def _reconnect_handler(self, res): ) await asyncio.sleep(reconnect_delay) - async def _connect_handler(self, res): + async def _connect_handler(self, res: Future[bool]) -> None: """Handle connection for StreamMagic.""" try: self.futures = {} @@ -199,7 +199,7 @@ async def _connect_handler(self, res): _LOGGER.error(ex, exc_info=True) @staticmethod - async def subscription_handler(queue, callback): + async def subscription_handler(queue: Queue, callback) -> None: """Handle subscriptions.""" try: while True: @@ -213,7 +213,7 @@ async def consumer_handler( ws: WebSocketClientProtocol, subscriptions: dict[str, list[Any]], futures: dict[str, list[asyncio.Future]], - ): + ) -> None: """Callback consumer handler.""" subscription_queues = {} subscription_tasks = {} @@ -245,7 +245,9 @@ async def consumer_handler( ): pass - async def _send(self, path, params=None): + async def _send( + self, path: str, params: Optional[dict[str, str | int | float | bool]] = None + ) -> None: """Send a command to the device.""" message = { "path": path, @@ -258,7 +260,9 @@ async def _send(self, path, params=None): _LOGGER.debug("Sending command: %s", message) await self.connection.send(json.dumps(message)) - async def request(self, path: str, params=None) -> Any: + async def request( + self, path: str, params: Optional[dict[str, str | int | float | bool]] = None + ) -> Any: res = self._loop.create_future() path_futures = self.futures.get(path, []) path_futures.append(res) @@ -392,28 +396,28 @@ async def get_preset_list(self) -> PresetList: data = await self.request(ep.PRESET_LIST) return PresetList.from_dict(data["params"]["data"]) - async def _async_handle_info(self, payload) -> None: + async def _async_handle_info(self, payload: dict[str, Any]) -> None: """Handle async info update.""" params = payload["params"] if "data" in params: self._info = Info.from_dict(params["data"]) await self.do_state_update_callbacks() - async def _async_handle_sources(self, payload) -> None: + async def _async_handle_sources(self, payload: dict[str, Any]) -> None: """Handle async sources update.""" params = payload["params"] if "data" in params: self.sources = [Source.from_dict(x) for x in params["data"]["sources"]] await self.do_state_update_callbacks() - async def _async_handle_zone_state(self, payload) -> None: + async def _async_handle_zone_state(self, payload: dict[str, Any]) -> None: """Handle async zone state update.""" params = payload["params"] if "data" in params: self._state = State.from_dict(params["data"]) await self.do_state_update_callbacks() - async def _async_handle_play_state(self, payload) -> None: + async def _async_handle_play_state(self, payload: dict[str, Any]) -> None: """Handle async zone state update.""" params = payload["params"] if "data" in params: @@ -421,7 +425,7 @@ async def _async_handle_play_state(self, payload) -> None: self.position_last_updated = datetime.now() await self.do_state_update_callbacks() - async def _async_handle_position(self, payload) -> None: + async def _async_handle_position(self, payload: dict[str, Any]) -> None: """Handle async position update.""" params = payload["params"] if "data" in params and params["data"]["position"] and self.play_state: @@ -429,35 +433,35 @@ async def _async_handle_position(self, payload) -> None: self.position_last_updated = datetime.now(UTC) await self.do_state_update_callbacks() - async def _async_handle_now_playing(self, payload) -> None: + async def _async_handle_now_playing(self, payload: dict[str, Any]) -> None: """Handle async now playing update.""" params = payload["params"] if "data" in params: self._now_playing = NowPlaying.from_dict(params["data"]) await self.do_state_update_callbacks() - async def _async_handle_audio_output(self, payload) -> None: + async def _async_handle_audio_output(self, payload: dict[str, Any]) -> None: """Handle async audio output update.""" params = payload["params"] if "data" in params: self._audio_output = AudioOutput.from_dict(params["data"]) await self.do_state_update_callbacks() - async def _async_handle_display(self, payload) -> None: + async def _async_handle_display(self, payload: dict[str, Any]) -> None: """Handle async display update.""" params = payload["params"] if "data" in params: self._display = Display.from_dict(params["data"]) await self.do_state_update_callbacks() - async def _async_handle_update(self, payload) -> None: + async def _async_handle_update(self, payload: dict[str, Any]) -> None: """Handle async display update.""" params = payload["params"] if "data" in params: self._update = Update.from_dict(params["data"]) await self.do_state_update_callbacks() - async def _async_handle_preset_list(self, payload) -> None: + async def _async_handle_preset_list(self, payload: dict[str, Any]) -> None: """Handle async preset list update.""" params = payload["params"] if "data" in params: @@ -549,14 +553,14 @@ async def stop(self) -> None: ep.PLAY_CONTROL, params={"match": "none", "zone": "ZONE1", "action": "stop"} ) - async def set_shuffle(self, shuffle: ShuffleMode): + async def set_shuffle(self, shuffle: ShuffleMode) -> None: """Set the shuffle of the device.""" await self.request( ep.PLAY_CONTROL, params={"match": "none", "zone": "ZONE1", "mode_shuffle": shuffle}, ) - async def set_repeat(self, repeat: RepeatMode): + async def set_repeat(self, repeat: RepeatMode) -> None: """Set the repeat of the device.""" await self.request( ep.PLAY_CONTROL, diff --git a/examples/basic.py b/examples/basic.py index bcb65de..1c04f86 100644 --- a/examples/basic.py +++ b/examples/basic.py @@ -5,7 +5,7 @@ HOST = "192.168.20.218" -async def main(): +async def main() -> None: """Basic demo entrypoint.""" client = StreamMagicClient("192.168.20.218") await client.connect() diff --git a/examples/subscribe.py b/examples/subscribe.py index d64f26e..d741314 100644 --- a/examples/subscribe.py +++ b/examples/subscribe.py @@ -6,7 +6,9 @@ HOST = "192.168.20.218" -async def on_state_change(client: StreamMagicClient, callback_type: CallbackType): +async def on_state_change( + client: StreamMagicClient, callback_type: CallbackType +) -> None: """Called when new information is received.""" print(f"Callback Type: {callback_type} {client.is_connected()}") print(f"System info: {client.info}") @@ -18,7 +20,7 @@ async def on_state_change(client: StreamMagicClient, callback_type: CallbackType print(f"Preset List: {client.preset_list}") -async def main(): +async def main() -> None: """Subscribe demo entrypoint.""" client = StreamMagicClient("192.168.20.218") await client.register_state_update_callbacks(on_state_change) diff --git a/poetry.lock b/poetry.lock index f608662..7c0d00b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -322,6 +322,69 @@ files = [ {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] +[[package]] +name = "mypy" +version = "1.13.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "orjson" version = "3.10.9" @@ -839,4 +902,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "76c57cc88992116cfa90f8cf2d1c9bdca0aac302a0e6e76052ad2f27f46657e8" +content-hash = "56177c131eaa318792160c4cf759122d22490073aedba9cc443bb24c32945e09" diff --git a/pyproject.toml b/pyproject.toml index 72410fc..1c28a2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "aiostreammagic" -version = "2.8.3" +version = "2.8.4" description = "An async python package for interfacing with Cambridge Audio / Stream Magic compatible streamers." authors = ["Noah Husby <32528627+noahhusby@users.noreply.github.com>"] maintainers = ["Noah Husby <32528627+noahhusby@users.noreply.github.com>"] @@ -18,11 +18,42 @@ orjson = ">=3.9.0" websockets = "^13.0.1" [tool.poetry.group.dev.dependencies] +mypy = "1.13.0" pytest = "8.3.3" pytest-asyncio = "0.24.0" pytest-cov = "5.0.0" ruff = "0.7.0" +[tool.mypy] +# Specify the target platform details in config, so your developers are +# free to run mypy on Windows, Linux, or macOS and get consistent +# results. +platform = "linux" +python_version = "3.11" + +# show error messages from unrelated files +follow_imports = "normal" + +# suppress errors about unsatisfied imports +ignore_missing_imports = true + +# be strict +check_untyped_defs = true +disallow_any_generics = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +strict_optional = true +warn_incomplete_stub = true +warn_no_return = true +warn_redundant_casts = true +warn_return_any = true +warn_unused_configs = true +warn_unused_ignores = true + [tool.poetry.group.docs.dependencies] pdoc = ">=14.7,<16.0"