Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 6 additions & 40 deletions .github/workflows/linters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,50 +8,16 @@ on:
- master
workflow_dispatch:

permissions:
contents: read

jobs:
linters:
name: Run Linters
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v6.0.2

- name: Debug GitHub Variables
run: |
echo "github.event_name: ${{ github.event_name }}"
echo "github.ref_name: ${{ github.ref_name }}"
echo "github.event.repository.default_branch: ${{ github.event.repository.default_branch }}"

- name: Setup Python 3 (with caching)
uses: actions/setup-python@v6.2.0
id: setup-python
with:
python-version: 3.x
cache: 'pip'
cache-dependency-path: |
pyproject.toml

- name: Install Linting Requirements
run: |
python -m pip install --upgrade pip
python -m pip install --group lint -e .

- name: Cache pre-commit and mypy
uses: actions/cache@v5.0.4
with:
path: |
~/.cache/pre-commit
.mypy_cache
.ruff_cache
key: ${{ runner.os }}-lint-py${{ steps.setup-python.outputs.python-version || '3.x' }}-${{ hashFiles('**/.pre-commit-config.yaml', '**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-lint-

- name: Run pre-commit
uses: pre-commit/action@v3.0.1

- name: Run mypy
run: mypy "./custom_components/places/" --install-types --non-interactive --config-file pyproject.toml
uses: actions/checkout@v6

- uses: pre-commit-ci/lite-action@v1.1.0
if: always()
- name: Run prek
uses: j178/prek-action@v2
43 changes: 25 additions & 18 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,37 @@ repos:
rev: v1.7.12
hooks:
- id: actionlint
# Note: shellcheck cannot directly parse YAML; actionlint extracts workflow
# shell blocks and calls shellcheck when available.
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.11.0.1
hooks:
- id: shellcheck
# Match by detected shell file type (extensions or shebang)
types: [shell]
args: ['-x']
additional_dependencies: ['github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.11.1']
- repo: https://github.com/codespell-project/codespell
rev: v2.4.2
hooks:
- id: codespell
args:
- --ignore-words-list=hass
additional_dependencies:
- tomli
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.20.0
hooks:
- id: mypy
language_version: python3.14
args: [--config-file=pyproject.toml]
additional_dependencies:
- 'aiohttp'
- 'homeassistant-stubs'
- 'pytest-asyncio'
- 'pytest-cov'
- 'pytest-homeassistant-custom-component'
- 'pytest'
- 'types-cachetools'
- 'types-cffi'
- 'types-greenlet'
- 'types-PyMySQL'
- 'types-pyRFC3339'
- 'types-python-dateutil'
- 'types-PyYAML'
- 'types-requests'
- 'types-setuptools'
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.8
hooks:
Expand All @@ -38,12 +54,3 @@ repos:
args: [--fix]
# Run the formatter.
- id: ruff-format

ci:
autofix_commit_msg: |
[pre-commit.ci] auto fixes from pre-commit hooks
autofix_prs: true
autoupdate_commit_msg: '[pre-commit.ci] pre-commit autoupdate'
autoupdate_schedule: weekly
skip: []
submodules: false
16 changes: 7 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
- Be concise and explain coding steps briefly when making code changes; include code snippets and tests where relevant.
- For non-trivial edits, provide a short plan. For small, low-risk edits, implement and include a one-line summary.
- Focus on a single conceptual change at a time when public APIs or multiple modules are affected.
- Maintain project style and Python 3.13+ compatibility. Target latest Home Assistant core.
- Maintain project style and Python 3.14+ compatibility. Target latest Home Assistant core.
- If deviating from these guidelines, explicitly state which guideline is deviated from and why.

## Agent permissions and venv policy
Expand Down Expand Up @@ -39,7 +39,7 @@
## Coding standards

- Add typing annotations to all functions and classes (including return types).
- Add or update docstrings for all files, classes and methods, including private methods. Method docstrings must be in NumPy format.
- Add or update docstrings for all files, classes and methods, including private methods. Method docstrings must be in Google Style.
- Preserve existing comments and keep imports at the top of files.
- When editing code, prefer fixing root causes over surface patches.
- Keep changes minimal and consistent with the codebase style.
Expand All @@ -54,15 +54,13 @@

## Local tooling (common commands)

- Use `pre-commit`, `mypy`, and `pytest` configured in the repo. You must run these inside `./.venv`.
- Prefer invoking tooling via `./.venv/bin/python -m ...` rather than relying on global/shell entry points (e.g., `pre-commit`).
- `ruff` is used for linting and formatting but should be called using `pre-commit`.
- Use `prek` and `pytest` configured in the repo. You must run these inside `./.venv`.
- Prefer invoking tooling via `./.venv/bin/python -m ...` rather than relying on global/shell entry points (e.g., `prek`).
- `ruff` is used for linting and formatting but should be called using `prek`.
- Run tests:
- `./.venv/bin/python -m pytest`
- Run pre-commit on all files (includes `ruff`):
- `./.venv/bin/python -m pre_commit run --all-files`
- Run mypy (use repo configuration):
- `./.venv/bin/python -m mypy`
- Run prek on all files (includes `ruff` and `mypy`):
- `./.venv/bin/python -m prek run -a`

## Testing

Expand Down
2 changes: 1 addition & 1 deletion custom_components/places/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def is_float(value: Any) -> bool:
return False
try:
float(value)
except (ValueError, TypeError):
except ValueError, TypeError:
return False
else:
return True
Expand Down
2 changes: 1 addition & 1 deletion custom_components/places/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ def get_attr_safe_float(self, attr: str | None, default: Any | None = None) -> f
return 0.0
try:
return float(value)
except (TypeError, ValueError):
except TypeError, ValueError:
return 0.0

def get_attr_safe_list(self, attr: str | None, default: Any | None = None) -> list:
Expand Down
13 changes: 7 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "places"
dynamic = ["version"]
description = "Places Home Assistant integration"
readme = "README.md"
requires-python = ">=3.13"
requires-python = ">=3.14"
dependencies = [
"aiohttp",
"cachetools",
Expand All @@ -20,7 +20,7 @@ include = ["custom_components*"]
lint = [
"homeassistant-stubs",
"mypy",
"pre-commit",
"prek",
"ruff",
"types-cachetools",
"types-cffi",
Expand Down Expand Up @@ -69,7 +69,7 @@ parallel = false
show_missing = true

[tool.mypy]
python_version = "3.13"
python_version = "3.14"
platform = "linux"
local_partial_types = true
strict_equality = true
Expand Down Expand Up @@ -105,8 +105,8 @@ line-length = 100
indent-width = 4
fix = true
force-exclude = true
target-version = "py313"
required-version = ">=0.8.0"
target-version = "py314"
required-version = ">=0.15.0"

[tool.ruff.format]
quote-style = "double"
Expand Down Expand Up @@ -351,4 +351,5 @@ split-on-trailing-comma = false
max-complexity = 25

[tool.ruff.lint.pydocstyle]
property-decorators = ["propcache.cached_property"]
convention = "google"
property-decorators = ["propcache.api.cached_property"]
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def _get_attr_safe_float_default(attr, default=None):
return float(default) if default is not None else 0.0
try:
return float(val)
except (TypeError, ValueError):
except TypeError, ValueError:
if isinstance(default, MagicMock):
return 0.0
return float(default) if default is not None else 0.0
Expand Down
40 changes: 32 additions & 8 deletions tests/test_update_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1031,10 +1031,17 @@ async def test_calculate_distances_not_all_attrs_set(
):
"""Test calculate_distances does NOT set distance attributes if any required attribute is blank."""
updater = make_updater(mock_hass, mock_config_entry, sensor)

# Patch is_attr_blank to return True for the blank_attr, False otherwise
sensor.is_attr_blank = lambda k: k == blank_attr
def is_attr_blank(key: str) -> bool:
return key == blank_attr

def set_attr(key: str, value: object) -> None:
sensor.attrs[key] = value

sensor.is_attr_blank = is_attr_blank
# Patch set_attr to update attrs
sensor.set_attr = lambda k, v: sensor.attrs.__setitem__(k, v)
sensor.set_attr = set_attr
await updater.calculate_distances()
# None of the distance attributes should be set
assert ATTR_DISTANCE_FROM_HOME_M not in sensor.attrs
Expand All @@ -1048,13 +1055,16 @@ async def test_calculate_distances_distance_from_home_m_blank(mock_hass, mock_co
updater = make_updater(mock_hass, mock_config_entry, sensor)

# Patch is_attr_blank so ATTR_DISTANCE_FROM_HOME_M is blank after calculation
def is_attr_blank(key):
def is_attr_blank(key: str) -> bool:
# Only ATTR_DISTANCE_FROM_HOME_M is blank
return key == ATTR_DISTANCE_FROM_HOME_M

def set_attr(key: str, value: object) -> None:
sensor.attrs[key] = value

sensor.is_attr_blank = is_attr_blank
# Patch set_attr to update attrs
sensor.set_attr = lambda k, v: sensor.attrs.__setitem__(k, v)
sensor.set_attr = set_attr
# Patch get_attr_safe_float to return valid floats
sensor.get_attr_safe_float = lambda k: 1.0
# Patch all required attributes to not blank except ATTR_DISTANCE_FROM_HOME_M
Expand Down Expand Up @@ -1086,22 +1096,36 @@ async def test_calculate_travel_distance_variants(
sensor.get_attr_safe_float = lambda k: 1.0

if mode == "missing_old_coord":
sensor.is_attr_blank = lambda k: k == blank_attr

def is_attr_blank(key: str) -> bool:
return key == blank_attr

def set_attr(key: str, value: object) -> None:
sensor.attrs[key] = value

sensor.is_attr_blank = is_attr_blank
# Ensure set_attr updates attrs for this branch
sensor.set_attr = lambda k, v: sensor.attrs.__setitem__(k, v)
sensor.set_attr = set_attr
await updater.calculate_travel_distance()
assert sensor.attrs[ATTR_DIRECTION_OF_TRAVEL] == expected_direction
assert sensor.attrs[ATTR_DISTANCE_TRAVELED_M] == 0
assert sensor.attrs[ATTR_DISTANCE_TRAVELED_MI] == 0
return

if mode == "blank_traveled_m":
sensor.is_attr_blank = lambda k: k == blank_attr

def is_attr_blank(key: str) -> bool:
return key == blank_attr

def set_attr(key: str, value: object) -> None:
sensor.attrs[key] = value

sensor.is_attr_blank = is_attr_blank
# Provide old coords so calculation proceeds
for attr in [ATTR_LATITUDE_OLD, ATTR_LONGITUDE_OLD]:
sensor.attrs[attr] = 1.0
# Ensure set_attr updates attrs for this branch
sensor.set_attr = lambda k, v: sensor.attrs.__setitem__(k, v)
sensor.set_attr = set_attr
await updater.calculate_travel_distance()
assert ATTR_DISTANCE_TRAVELED_M in sensor.attrs
assert ATTR_DISTANCE_TRAVELED_MI not in sensor.attrs
Expand Down
Loading