Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add type annotations to controllers/receiver.py #796

Merged
merged 1 commit into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ disallow_untyped_decorators = true
disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true
files = pychromecast/config.py, pychromecast/const.py, pychromecast/dial.py, pychromecast/discovery.py, pychromecast/error.py, pychromecast/models.py, pychromecast/response_handler.py, pychromecast/controllers/__init__.py
files = pychromecast/config.py, pychromecast/const.py, pychromecast/dial.py, pychromecast/discovery.py, pychromecast/error.py, pychromecast/models.py, pychromecast/response_handler.py, pychromecast/controllers/__init__.py, pychromecast/controllers/receiver.py
2 changes: 1 addition & 1 deletion pychromecast/controllers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def launch(
if self._socket_client is None:
raise ControllerNotRegistered

self._socket_client.receiver_controller.launch_app( # type: ignore[no-untyped-call]
self._socket_client.receiver_controller.launch_app(
self.supporting_app_id,
force_launch=force_launch,
callback_function=callback_function,
Expand Down
79 changes: 49 additions & 30 deletions pychromecast/controllers/receiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
REQUEST_TIMEOUT,
SESSION_ID,
)
from ..generated.cast_channel_pb2 import ( # pylint: disable=no-name-in-module
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this always be the case for generated modules that pylint doesn't understand them?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like it, yes.
Pylint actually very recently gained support for pyi stub files pylint-dev/pylint#4987, but I guess the support is still rough around the edges.

For example, this comment mentions something very similar: pylint-dev/pylint#4987 (comment)

I think this is why it doesn't work: pylint-dev/pylint#9185

CastMessage,
)
from ..response_handler import WaitResponse, chain_on_success
from . import BaseController
from . import BaseController, CallbackType

APP_ID = "appId"
ERROR_REASON = "reason"
Expand Down Expand Up @@ -64,15 +67,15 @@ class CastStatusListener(abc.ABC):
"""Listener for receiving cast status events."""

@abc.abstractmethod
def new_cast_status(self, status: CastStatus):
def new_cast_status(self, status: CastStatus) -> None:
"""Updated cast status."""


class LaunchErrorListener(abc.ABC):
"""Listener for receiving launch error events."""

@abc.abstractmethod
def new_launch_error(self, status: LaunchFailure):
def new_launch_error(self, status: LaunchFailure) -> None:
"""Launch error."""


Expand All @@ -83,27 +86,27 @@ class ReceiverController(BaseController):
:param cast_type: Type of Chromecast device.
"""

def __init__(self, cast_type=CAST_TYPE_CHROMECAST):
def __init__(self, cast_type: str = CAST_TYPE_CHROMECAST) -> None:
super().__init__(NS_RECEIVER, target_platform=True)

self.status = None
self.launch_failure = None
self.status: CastStatus | None = None
self.launch_failure: LaunchFailure | None = None
self.cast_type = cast_type

self._status_listeners = []
self._launch_error_listeners = []
self._status_listeners: list[CastStatusListener] = []
self._launch_error_listeners: list[LaunchErrorListener] = []

def disconnected(self):
def disconnected(self) -> None:
"""Called when disconnected. Will erase status."""
self.logger.info("Receiver:channel_disconnected")
self.status = None

@property
def app_id(self):
def app_id(self) -> str | None:
"""Convenience method to retrieve current app id."""
return self.status.app_id if self.status else None

def receive_message(self, _message, data: dict):
def receive_message(self, _message: CastMessage, data: dict) -> bool:
"""
Called when a receiver message is received.

Expand All @@ -121,26 +124,36 @@ def receive_message(self, _message, data: dict):

return False

def register_status_listener(self, listener: CastStatusListener):
def register_status_listener(self, listener: CastStatusListener) -> None:
"""Register a status listener for when a new Chromecast status
has been received. Listeners will be called with
listener.new_cast_status(status)"""
self._status_listeners.append(listener)

def register_launch_error_listener(self, listener: LaunchErrorListener):
def register_launch_error_listener(self, listener: LaunchErrorListener) -> None:
"""Register a listener for when a new launch error message
has been received. Listeners will be called with
listener.new_launch_error(launch_failure)"""
self._launch_error_listeners.append(listener)

def update_status(self, *, callback_function=None):
def update_status(
self,
*,
callback_function: CallbackType | None = None,
) -> None:
"""Sends a message to the Chromecast to update the status."""
self.logger.debug("Receiver:Updating status")
self.send_message(
{MESSAGE_TYPE: TYPE_GET_STATUS}, callback_function=callback_function
)

def launch_app(self, app_id, *, force_launch=False, callback_function=None):
def launch_app(
self,
app_id: str,
*,
force_launch: bool = False,
callback_function: CallbackType | None = None,
) -> None:
"""Launches an app on the Chromecast.

Will only launch if it is not currently running unless
Expand All @@ -157,7 +170,12 @@ def launch_app(self, app_id, *, force_launch=False, callback_function=None):
else:
self._send_launch_message(app_id, force_launch, callback_function)

def _send_launch_message(self, app_id, force_launch=False, callback_function=None):
def _send_launch_message(
self,
app_id: str,
force_launch: bool,
callback_function: CallbackType | None,
) -> None:
if force_launch or self.app_id != app_id:
self.logger.info("Receiver:Launching app %s", app_id)

Expand Down Expand Up @@ -186,7 +204,11 @@ def handle_launch_response(msg_sent: bool, response: dict | None) -> None:
if callback_function:
callback_function(True, None)

def stop_app(self, *, callback_function=None):
def stop_app(
self,
*,
callback_function: CallbackType | None = None,
) -> None:
"""Stops the current running app on the Chromecast."""
self.logger.info("Receiver:Stopping current app '%s'", self.app_id)
return self.send_message(
Expand All @@ -195,7 +217,7 @@ def stop_app(self, *, callback_function=None):
callback_function=callback_function,
)

def set_volume(self, volume, timeout=REQUEST_TIMEOUT):
def set_volume(self, volume: float, timeout: float = REQUEST_TIMEOUT) -> float:
"""Allows to set volume. Should be value between 0..1.
Returns the new volume.

Expand All @@ -210,7 +232,7 @@ def set_volume(self, volume, timeout=REQUEST_TIMEOUT):
response_handler.wait_response()
return volume

def set_volume_muted(self, muted, timeout=REQUEST_TIMEOUT):
def set_volume_muted(self, muted: bool, timeout: float = REQUEST_TIMEOUT) -> None:
"""Allows to mute volume."""
response_handler = WaitResponse(timeout)
self.send_message(
Expand All @@ -220,20 +242,20 @@ def set_volume_muted(self, muted, timeout=REQUEST_TIMEOUT):
response_handler.wait_response()

@staticmethod
def _parse_status(data, cast_type):
def _parse_status(data: dict, cast_type: str) -> CastStatus:
"""
Parses a STATUS message and returns a CastStatus object.

:type data: dict
:param cast_type: Type of Chromecast.
:rtype: CastStatus
"""
data = data.get("status", {})
status_data: dict = data.get("status", {})

volume_data = data.get("volume", {})
volume_data: dict = status_data.get("volume", {})

try:
app_data = data["applications"][0]
app_data: dict = status_data["applications"][0]
except (KeyError, IndexError):
app_data = {}

Expand All @@ -255,16 +277,13 @@ def _parse_status(data, cast_type):
)
return status

def _process_get_status(self, data):
def _process_get_status(self, data: dict) -> None:
"""Processes a received STATUS message and notifies listeners."""
status = self._parse_status(data, self.cast_type)
self.status = status

self.logger.debug("Received status: %s", self.status)
self._report_status()

def _report_status(self):
"""Reports the current status to all listeners."""
for listener in self._status_listeners:
try:
listener.new_cast_status(self.status)
Expand All @@ -274,7 +293,7 @@ def _report_status(self):
)

@staticmethod
def _parse_launch_error(data):
def _parse_launch_error(data: dict) -> LaunchFailure:
"""
Parses a LAUNCH_ERROR message and returns a LaunchFailure object.

Expand All @@ -285,7 +304,7 @@ def _parse_launch_error(data):
data.get(ERROR_REASON, None), data.get(APP_ID), data.get(REQUEST_ID)
)

def _process_launch_error(self, data):
def _process_launch_error(self, data: dict) -> None:
"""
Processes a received LAUNCH_ERROR message and notifies listeners.
"""
Expand All @@ -302,7 +321,7 @@ def _process_launch_error(self, data):
"Exception thrown when calling launch error listener"
)

def tear_down(self):
def tear_down(self) -> None:
"""Called when controller is destroyed."""
super().tear_down()

Expand Down