Skip to content

Commit

Permalink
Version 1.0.1 (#97)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewsayre authored Jan 26, 2025
2 parents ba02e18 + d03b080 commit e04cd54
Show file tree
Hide file tree
Showing 45 changed files with 3,567 additions and 662 deletions.
12 changes: 0 additions & 12 deletions .github/ISSUE_TEMPLATE.md

This file was deleted.

38 changes: 38 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
name: Report a bug in pyheos
about: Create a report to help us improve the library
title: ""
labels: bug, triage
assignees: ""
---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior.

**Expected behavior**
A clear and concise description of what you expected to happen.

**Environment (please complete the following information):**

- pyheos version: [e.g. 1.0.0]
- python version: [e.g. 3.12]
- HEOS device model: [e.g. HEOS Drive]
- HEOS device firmware version: [e.g. 3.34.620]

**Logs**

```
```

**Traceback (if applicable)**

```
```

**Additional context**
Add any other context about the problem here.
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
blank_issues_enabled: false
20 changes: 20 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.
12 changes: 7 additions & 5 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,18 @@ jobs:
python -m pip install --upgrade pip
pip install uv
uv pip install -r requirements.txt -r test-requirements.txt --upgrade --system
- name: Set up check hooks
run: pre-commit install-hooks
- name: Spelling (codespell)
run: codespell
run: pre-commit run --all-files --hook-stage manual codespell --show-diff-on-failure
- name: Lint (Ruff)
run: ruff check --select I
run: pre-commit run --all-files --hook-stage manual ruff --show-diff-on-failure
- name: Format (Ruff)
run: ruff format --check
run: pre-commit run --all-files --hook-stage manual ruff-format --show-diff-on-failure
- name: Format (Pylint)
run: pylint pyheos tests
run: pre-commit run --all-files --hook-stage manual pylint --show-diff-on-failure
- name: Typing my[py]
run: mypy pyheos tests
run: pre-commit run --all-files --hook-stage manual mypy --show-diff-on-failure

tests:
name: "Run tests on ${{ matrix.python-version }}"
Expand Down
39 changes: 39 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
repos:
- repo: https://github.com/codespell-project/codespell
rev: v2.4.0
hooks:
- id: codespell
args:
- --skip="./.*,*.csv,*.json,*.ambr"
exclude_types: [csv, json, html]
exclude: ^tests/fixtures/|^tests/snapshots/
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.3
hooks:
- id: ruff
args:
- --extend-select=I
- --fix
- id: ruff-format
- repo: local
hooks:
- id: pylint
name: pylint
entry: pylint
language: system
types: [python]
require_serial: true
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.14.1
hooks:
- id: mypy
args: [--strict]
additional_dependencies:
- pydantic==2.10.5
- pylint==3.3.3
- pylint-per-file-ignores==1.4.0
- pytest==8.3.4
- pytest-asyncio==0.25.2
- pytest-cov==6.0.0
- pytest-timeout==2.3.1
- syrupy==4.8.1
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,8 @@
".ruff_cache": true,
".pytest_cache": true,
".mypy_cache": true
},
"yaml.schemas": {
"https://json.schemastore.org/github-issue-config.json": "file:///workspaces/pyheos/.github/ISSUE_TEMPLATE/config.yml"
}
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ This class encapsulates the options and configuration for connecting to a HEOS s
#### `pyheos.HeosOptions(host, *, timeout, heart_beat, heart_beat_interval, dispatcher, auto_reconnect, auto_reconnect_delay, auto_reconnect_max_attempts, credentials)`

- `host: str`: A host name or IP address of a HEOS-capable device. This parameter is required.
- `timeout: float`: The timeout in seconds for opening a connectoin and issuing commands to the device. Default is `pyheos.const.DEFAULT_TIMEOUT = 10.0`. This parameter is required.
- `timeout: float`: The timeout in seconds for opening a connection and issuing commands to the device. Default is `pyheos.const.DEFAULT_TIMEOUT = 10.0`. This parameter is required.
- `heart_beat: bool`: Set to `True` to enable heart beat messages, `False` to disable. Used in conjunction with `heart_beat_delay`. The default is `True`.
- `heart_beat_interval: float`: The interval in seconds between heart beat messages. Used in conjunction with `heart_beat`. Default is `pyheos.const.DEFAULT_HEART_BEAT = 10.0`
- `events: bool`: Set to `True` to enable event updates, `False` to disable. The default is `True`.
Expand Down
15 changes: 15 additions & 0 deletions pyheos/abc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Define abstract base classes for HEOS."""

from abc import ABC
from typing import Any


class RemoveHeosFieldABC(ABC):
"""Define an abstract base class that removes the 'heos' from dataclass's fields list to prevent serialization."""

def __post_init__(self, *args: Any, **kwargs: Any) -> None:
"""Post initialize the player."""
# Prevent the heos instance from being serialized
fields = self.__dataclass_fields__.copy() # type: ignore[has-type] # pylint: disable=access-member-before-definition
del fields["heos"]
self.__dataclass_fields__ = fields
23 changes: 16 additions & 7 deletions pyheos/command/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,22 +166,31 @@ def optional_int(value: str | None) -> int | None:
return None


def parse_enum(
key: str, data: dict[str, Any], enum_type: type[TEnum], default: TEnum
) -> TEnum:
"""Parse an enum value from the provided data. This is a safe operation that will return the default value if the key is missing or the value is not recognized."""
def parse_optional_enum(
key: str, data: dict[str, Any], enum_type: type[TEnum]
) -> TEnum | None:
"""Parse an enum value from the provided data. This is a safe operation that will return None if the key is missing or the value is not recognized."""
value = data.get(key)
if value is None:
return default
return None
try:
return enum_type(value)
except ValueError:
_LOGGER.warning(
"Unrecognized '%s' value: '%s', using default value: '%s'. Full data: %s. %s",
"Unrecognized '%s' value: '%s'. Full data: %s. %s",
key,
value,
default,
data,
REPORT_ISSUE_TEXT,
)
return None


def parse_enum(
key: str, data: dict[str, Any], enum_type: type[TEnum], default: TEnum
) -> TEnum:
"""Parse an enum value from the provided data. This is a safe operation that will return the default value if the key is missing or the value is not recognized."""
value = parse_optional_enum(key, data, enum_type)
if value is None:
return default
return value
2 changes: 1 addition & 1 deletion pyheos/command/browse.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ async def get_music_sources(
HeosCommand(c.COMMAND_BROWSE_GET_SOURCES, params)
)
self._music_sources.clear()
for data in cast(Sequence[dict], message.payload):
for data in cast(Sequence[dict[str, Any]], message.payload):
source = MediaMusicSource.from_data(data, cast("Heos", self))
self._music_sources[source.source_id] = source
self._music_sources_loaded = True
Expand Down
4 changes: 2 additions & 2 deletions pyheos/command/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ async def get_groups(self, *, refresh: bool = False) -> dict[int, HeosGroup]:
References:
4.3.1 Get Groups"""
if not self._groups_loaded or refresh:
groups = {}
groups: dict[int, HeosGroup] = {}
result = await self._connection.command(HeosCommand(c.COMMAND_GET_GROUPS))
payload = cast(Sequence[dict], result.payload)
payload = cast(Sequence[dict[str, Any]], result.payload)
for data in payload:
group = HeosGroup._from_data(data, cast("Heos", self))
groups[group.group_id] = group
Expand Down
15 changes: 4 additions & 11 deletions pyheos/command/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,8 @@
from pyheos.command.connection import ConnectionMixin
from pyheos.media import QueueItem
from pyheos.message import HeosCommand
from pyheos.player import (
HeosNowPlayingMedia,
HeosPlayer,
PlayerUpdateResult,
PlayMode,
PlayState,
)
from pyheos.types import RepeatType
from pyheos.player import HeosNowPlayingMedia, HeosPlayer, PlayerUpdateResult, PlayMode
from pyheos.types import PlayState, RepeatType

if TYPE_CHECKING:
from pyheos.heos import Heos
Expand All @@ -46,7 +40,6 @@ async def get_players(self, *, refresh: bool = False) -> dict[int, HeosPlayer]:
References:
4.2.1 Get Players"""
# get players and pull initial state
if not self._players_loaded or refresh:
await self.load_players()
return self._players
Expand Down Expand Up @@ -103,7 +96,7 @@ async def load_players(self) -> PlayerUpdateResult:

players: dict[int, HeosPlayer] = {}
response = await self._connection.command(HeosCommand(c.COMMAND_GET_PLAYERS))
payload = cast(Sequence[dict], response.payload)
payload = cast(Sequence[dict[str, str]], response.payload)
existing = list(self._players.values())
for player_data in payload:
player_id = int(player_data[c.ATTR_PLAYER_ID])
Expand Down Expand Up @@ -464,7 +457,7 @@ async def player_get_quick_selects(self, player_id: int) -> dict[int, str]:
)
return {
int(data[c.ATTR_ID]): data[c.ATTR_NAME]
for data in cast(list[dict], result.payload)
for data in cast(list[dict[str, Any]], result.payload)
}

async def player_check_update(self, player_id: int) -> bool:
Expand Down
2 changes: 1 addition & 1 deletion pyheos/command/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ async def get_system_info(self) -> HeosSystem:
References:
4.2.1 Get Players"""
response = await self._connection.command(HeosCommand(c.COMMAND_GET_PLAYERS))
payload = cast(Sequence[dict], response.payload)
payload = cast(Sequence[dict[str, Any]], response.payload)
hosts = list([HeosHost._from_data(item) for item in payload])
host = next(host for host in hosts if host.ip_address == self._options.host)
return HeosSystem(self._signed_in_username, host, hosts)
Loading

0 comments on commit e04cd54

Please sign in to comment.