Revert unique id for now, added dev files

This commit is contained in:
Ron Klinkien
2024-04-06 14:13:10 +02:00
parent cb16c0198c
commit b827597dab
11 changed files with 381 additions and 31 deletions

56
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,56 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: CI
on:
push:
branches:
- main
pull_request: ~
schedule:
- cron: "0 0 * * *"
env:
DEFAULT_PYTHON: "3.11"
jobs:
validate-hacs:
runs-on: "ubuntu-latest"
name: Validate with HACS
steps:
- uses: "actions/checkout@v4"
- name: HACS validation
uses: "hacs/action@main"
with:
category: "integration"
validate-hassfest:
runs-on: "ubuntu-latest"
name: Validate with Hassfest
steps:
- uses: "actions/checkout@v4"
- name: Hassfest validation
uses: "home-assistant/actions/hassfest@master"
code-quality:
runs-on: "ubuntu-latest"
name: Check code quality
steps:
- uses: "actions/checkout@v4"
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache: "pip"
- name: Install dependencies
run: |
pip install -r requirements.txt
# Following steps cannot run by pre-commit.ci as repo = local
- name: Run mypy
run: mypy custom_components/
- name: Pylint review
run: pylint custom_components/

89
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,89 @@
ci:
skip:
- mypy
- pylint
default_language_version:
python: python3.11
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v3.15.0
hooks:
- id: pyupgrade
args: [--py310-plus]
- repo: https://github.com/psf/black
rev: 23.12.1
hooks:
- id: black
args:
- --safe
- --quiet
<<: &python-files-with-tests
files: ^((custom_components|tests)/.+)?[^/]+\.py$
- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
hooks:
- id: flake8
additional_dependencies:
- flake8-docstrings==1.6.0
- pydocstyle==6.1.1
<<: &python-files
files: ^(custom_components/.+)?[^/]+\.py$
- repo: https://github.com/PyCQA/bandit
rev: 1.7.6
hooks:
- id: bandit
args:
- --quiet
- --format=custom
- --configfile=bandit.yaml
<<: *python-files-with-tests
- repo: https://github.com/PyCQA/isort
rev: 5.13.2
hooks:
- id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-executables-have-shebangs
- id: check-merge-conflict
- id: detect-private-key
- id: no-commit-to-branch
- id: requirements-txt-fixer
- id: mixed-line-ending
args:
- --fix=lf
stages: [manual]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
hooks:
- id: prettier
additional_dependencies:
- prettier@2.7.1
- prettier-plugin-sort-json@0.0.3
exclude_types:
- python
exclude: manifest\.json$
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.33.0
hooks:
- id: yamllint
- repo: local
hooks:
# Run mypy through our wrapper script in order to get the possible
# pyenv and/or virtualenv activated; it may not have been e.g. if
# committing from a GUI tool that was not launched from an activated
# shell.
- id: mypy
name: Check with mypy
entry: scripts/run-in-env.sh mypy
language: script
types: [python]
<<: *python-files
- id: pylint
name: Check with pylint
entry: scripts/run-in-env.sh pylint
language: script
types: [python]
<<: *python-files

3
.prettierrc Normal file
View File

@@ -0,0 +1,3 @@
{
"jsonRecursiveSort": true
}

59
.yamlllint Normal file
View File

@@ -0,0 +1,59 @@
rules:
braces:
level: error
min-spaces-inside: 0
max-spaces-inside: 1
min-spaces-inside-empty: -1
max-spaces-inside-empty: -1
brackets:
level: error
min-spaces-inside: 0
max-spaces-inside: 0
min-spaces-inside-empty: -1
max-spaces-inside-empty: -1
colons:
level: error
max-spaces-before: 0
max-spaces-after: 1
commas:
level: error
max-spaces-before: 0
min-spaces-after: 1
max-spaces-after: 1
comments:
level: error
require-starting-space: true
min-spaces-from-content: 2
comments-indentation:
level: error
document-end:
level: error
present: false
document-start:
level: error
present: false
empty-lines:
level: error
max: 1
max-start: 0
max-end: 1
hyphens:
level: error
max-spaces-after: 1
indentation:
level: error
spaces: 2
indent-sequences: true
check-multi-line-strings: false
key-duplicates:
level: error
line-length: disable
new-line-at-end-of-file:
level: error
new-lines:
level: error
type: unix
trailing-spaces:
level: error
truthy:
disable

20
bandit.yaml Normal file
View File

@@ -0,0 +1,20 @@
# https://bandit.readthedocs.io/en/latest/config.html
tests:
- B103
- B108
- B306
- B307
- B313
- B314
- B315
- B316
- B317
- B318
- B319
- B320
- B601
- B602
- B604
- B608
- B609

View File

@@ -9,7 +9,6 @@ import datetime
from tzlocal import get_localzone
from homeassistant.components.sensor import (
ENTITY_ID_FORMAT,
SensorEntity,
SensorDeviceClass,
SensorStateClass,
@@ -20,11 +19,10 @@ from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_ID,
)
from homeassistant.const import CONF_ID, CONF_USERNAME
from homeassistant.const import ATTR_ATTRIBUTION, CONF_ID
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity import DeviceInfo, generate_entity_id
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
@@ -50,14 +48,14 @@ async def async_setup_entry(
coordinator: DataUpdateCoordinator = hass.data[GARMIN_DOMAIN][entry.entry_id][
DATA_COORDINATOR
]
device_id = entry.data[CONF_ID]
user_identifier = entry.data[CONF_USERNAME].split("@")[0]
unique_id = entry.data[CONF_ID]
entities = []
for (
sensor_type,
(name, unit, icon, device_class, state_class, enabled_by_default),
) in GARMIN_ENTITY_LIST.items():
_LOGGER.debug(
"Registering entity: %s, %s, %s, %s, %s, %s, %s",
sensor_type,
@@ -71,8 +69,7 @@ async def async_setup_entry(
entities.append(
GarminConnectSensor(
coordinator,
device_id,
user_identifier,
unique_id,
sensor_type,
name,
unit,
@@ -87,8 +84,7 @@ async def async_setup_entry(
entities.append(
GarminConnectGearSensor(
coordinator,
device_id,
user_identifier,
unique_id,
gear_item[GEAR.UUID],
gear_item["gearTypeName"],
gear_item["displayName"],
@@ -141,10 +137,9 @@ class GarminConnectSensor(CoordinatorEntity, SensorEntity):
def __init__(
self,
coordinator,
device_id,
user_identifier,
unique_id,
sensor_type,
sensor_name,
name,
unit,
icon,
device_class,
@@ -154,22 +149,17 @@ class GarminConnectSensor(CoordinatorEntity, SensorEntity):
"""Initialize a Garmin Connect sensor."""
super().__init__(coordinator)
self._device_id = device_id
self._unique_id = unique_id
self._type = sensor_type
self._device_class = device_class
self._state_class = state_class
self._enabled_default = enabled_default
self._attr_name = f"{user_identifier} {sensor_name}"
self.entity_id = generate_entity_id(
ENTITY_ID_FORMAT,
f"{GARMIN_DOMAIN} {self._attr_name}",
hass=coordinator.hass,
)
self._attr_name = name
self._attr_device_class = self._device_class
self._attr_icon = icon
self._attr_native_unit_of_measurement = unit
self._attr_unique_id = f"{GARMIN_DOMAIN}_{self._device_id}_{self._type}"
self._attr_unique_id = f"{self._unique_id}_{self._type}"
self._attr_state_class = state_class
@property
@@ -233,7 +223,7 @@ class GarminConnectSensor(CoordinatorEntity, SensorEntity):
def device_info(self) -> DeviceInfo:
"""Return device information."""
return {
"identifiers": {(GARMIN_DOMAIN, self._device_id)},
"identifiers": {(GARMIN_DOMAIN, self._unique_id)},
"name": "Garmin Connect",
"manufacturer": "Garmin Connect",
}
@@ -259,28 +249,27 @@ class GarminConnectGearSensor(CoordinatorEntity, SensorEntity):
def __init__(
self,
coordinator,
device_id,
user_identifier,
unique_id,
uuid,
sensor_type,
sensor_name,
name,
device_class: None,
enabled_default: bool = True,
):
"""Initialize a Garmin Connect sensor."""
super().__init__(coordinator)
self._device_id = device_id
self._unique_id = unique_id
self._type = sensor_type
self._uuid = uuid
self._device_class = device_class
self._enabled_default = enabled_default
self._attr_name = f"{user_identifier} {sensor_name}"
self._attr_name = name
self._attr_device_class = self._device_class
self._attr_icon = GEAR_ICONS[sensor_type]
self._attr_native_unit_of_measurement = UnitOfLength.KILOMETERS
self._attr_unique_id = f"{GARMIN_DOMAIN}_{self._device_id}_{self._uuid}"
self._attr_unique_id = f"{self._unique_id}_{self._uuid}"
self._attr_state_class = SensorStateClass.TOTAL
self._attr_device_class = "garmin_gear"
@@ -348,7 +337,7 @@ class GarminConnectGearSensor(CoordinatorEntity, SensorEntity):
def device_info(self) -> DeviceInfo:
"""Return device information."""
return {
"identifiers": {(GARMIN_DOMAIN, self._device_id)},
"identifiers": {(GARMIN_DOMAIN, self._unique_id)},
"name": "Garmin Connect",
"manufacturer": "Garmin Connect",
}
@@ -382,4 +371,4 @@ class GarminConnectGearSensor(CoordinatorEntity, SensorEntity):
lambda d: d[GEAR.UUID] == self.uuid and d["defaultGear"] is True,
self.coordinator.data["gear_defaults"],
)
)
)

19
mypy.ini Normal file
View File

@@ -0,0 +1,19 @@
[mypy]
python_version = 3.11
show_error_codes = true
follow_imports = silent
ignore_missing_imports = true
strict_equality = true
warn_incomplete_stub = true
warn_redundant_casts = true
warn_unused_configs = true
warn_unused_ignores = true
check_untyped_defs = 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
warn_return_any = true
warn_unreachable = true

81
pylintrc Normal file
View File

@@ -0,0 +1,81 @@
[MASTER]
ignore=tests
# Use a conservative default here; 2 should speed up most setups and not hurt
# any too bad. Override on command line as appropriate.
jobs=2
# Return non-zero exit code if any of these messages/categories are detected,
# even if score is above --fail-under value. Syntax same as enable. Messages
# specified are enabled, while categories only check already-enabled messages.
fail-on=
useless-suppression,
# Specify a score threshold to be exceeded before program exits with error.
fail-under=10.0
# List of plugins (as comma separated values of python module names) to load,
# usually to register additional checkers.
# load-plugins=
# Pickle collected data for later comparisons.
persistent=no
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
# for backward compatibility.)
extension-pkg-whitelist=ciso8601,
cv2
[BASIC]
good-names=i,j,k,ex,_,T,x,y,id
[MESSAGES CONTROL]
# Reasons disabled:
# format - handled by black
# duplicate-code - unavoidable
# cyclic-import - doesn't test if both import on load
# too-many-* - are not enforced for the sake of readability
# abstract-method - with intro of async there are always methods missing
# inconsistent-return-statements - doesn't handle raise
# wrong-import-order - isort guards this
disable=
format,
abstract-class-little-used,
abstract-method,
cyclic-import,
duplicate-code,
inconsistent-return-statements,
too-many-instance-attributes,
wrong-import-order,
too-few-public-methods
# enable useless-suppression temporarily every now and then to clean them up
enable=
useless-suppression,
use-symbolic-message-instead,
[REPORTS]
score=no
[REFACTORING]
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit,argparse.parse_error
[FORMAT]
expected-line-ending-format=LF
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "BaseException, Exception".
overgeneral-exceptions=BaseException,
Exception

View File

@@ -1,4 +1,8 @@
colorlog==6.8.2
homeassistant==2024.1.0
pip>=21.0,<24.1
ruff==0.3.5
ruff==0.3.5
mypy==1.8.0
pre-commit==3.6.0
pylint==3.0.3
types-cachetools

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -e
python3 -m pip \
install \
--upgrade \
--disable-pip-version-check \
"${@}"

21
setup.cfg Normal file
View File

@@ -0,0 +1,21 @@
[flake8]
exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build
doctests = True
# To work with Black
max-line-length = 88
# E501: line too long
# W503: Line break occurred before a binary operator
# E203: Whitespace before ':'
# D202 No blank lines allowed after function docstring
# D107 Missing docstring in __init__
ignore =
E501,
W503,
E203,
D202,
D107
[isort]
# https://github.com/timothycrosley/isort
# https://github.com/timothycrosley/isort/wiki/isort-Settings
profile = black