mirror of
https://github.com/cyberjunky/home-assistant-garmin_connect.git
synced 2026-01-05 02:54:18 -05:00
Revert unique id for now, added dev files
This commit is contained in:
56
.github/workflows/ci.yml
vendored
Normal file
56
.github/workflows/ci.yml
vendored
Normal 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
89
.pre-commit-config.yaml
Normal 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
3
.prettierrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"jsonRecursiveSort": true
|
||||
}
|
||||
59
.yamlllint
Normal file
59
.yamlllint
Normal 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
20
bandit.yaml
Normal 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
|
||||
@@ -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
19
mypy.ini
Normal 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
81
pylintrc
Normal 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
|
||||
@@ -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
|
||||
9
scripts/install/pip_packages
Normal file
9
scripts/install/pip_packages
Normal 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
21
setup.cfg
Normal 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
|
||||
Reference in New Issue
Block a user