Skip to content

Commit

Permalink
Merge pull request #160 from jschlyter/ruff
Browse files Browse the repository at this point in the history
Lint and format with ruff
  • Loading branch information
jschlyter authored Oct 16, 2024
2 parents e7537e2 + b732e8f commit 9855e61
Show file tree
Hide file tree
Showing 16 changed files with 133 additions and 60 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Tests

on:
- push
- pull_request

jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: chartboost/ruff-action@v1
15 changes: 15 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-merge-conflict
- id: debug-statements
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
hooks:
- id: ruff
- id: ruff-format
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"ruff.lineLength": 88,
"editor.defaultFormatter": "charliermarsh.ruff"
}
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
SOURCE= custom_components


all:

lint:
ruff check $(SOURCE)

reformat:
ruff check --select I --fix $(SOURCE)
ruff format $(SOURCE)
21 changes: 10 additions & 11 deletions custom_components/polestar_api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
"""Polestar EV integration."""

import asyncio
from asyncio import timeout
import logging
from asyncio import timeout

from aiohttp import ClientConnectionError
import httpx

from aiohttp import ClientConnectionError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
Expand Down Expand Up @@ -95,17 +94,17 @@ async def polestar_setup(
"""Create a Polestar instance only once."""

try:
with timeout(TIMEOUT):
async with timeout(TIMEOUT):
device = Polestar(hass, name, username, password)
await device.init()
except asyncio.TimeoutError:
except asyncio.TimeoutError as exc:
_LOGGER.debug("Connection to %s timed out", name)
raise ConfigEntryNotReady
except ClientConnectionError as e:
_LOGGER.debug("ClientConnectionError to %s %s", name, str(e))
raise ConfigEntryNotReady
except Exception as e: # pylint: disable=broad-except
_LOGGER.error("Unexpected error creating device %s %s", name, str(e))
raise ConfigEntryNotReady from exc
except ClientConnectionError as exc:
_LOGGER.debug("ClientConnectionError to %s %s", name, str(exc))
raise ConfigEntryNotReady from exc
except Exception as exc: # pylint: disable=broad-except
_LOGGER.error("Unexpected error creating device %s %s", name, str(exc))
return None

return device
33 changes: 17 additions & 16 deletions custom_components/polestar_api/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
import asyncio
import logging

from aiohttp import ClientError
import voluptuous as vol

from aiohttp import ClientError
from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME

Expand All @@ -25,35 +24,33 @@ class FlowHandler(config_entries.ConfigFlow):
async def _create_entry(self, username: str, password: str) -> None:
"""Register new entry."""
return self.async_create_entry(
title='Polestar EV',
title="Polestar EV",
data={
CONF_USERNAME: username,
CONF_PASSWORD: password,
}
},
)

async def _create_device(self, username: str, password: str) -> None:
"""Create device."""

try:
device = Polestar(
self.hass,
username,
password)
device = Polestar(self.hass, username, password)
await device.init()

# check if we have a token, otherwise throw exception
if device.polestarApi.auth.access_token is None:
_LOGGER.exception(
"No token, Could be wrong credentials (invalid email or password))")
"No token, Could be wrong credentials (invalid email or password))"
)
return self.async_abort(reason="no_token")

except asyncio.TimeoutError:
return self.async_abort(reason="api_timeout")
except ClientError:
_LOGGER.exception("ClientError")
return self.async_abort(reason="api_failed")
except Exception as e: # pylint: disable=broad-except
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected error creating device")
return self.async_abort(reason="api_failed")

Expand All @@ -63,13 +60,17 @@ async def async_step_user(self, user_input: dict = None) -> None:
"""User initiated config flow."""
if user_input is None:
return self.async_show_form(
step_id="user", data_schema=vol.Schema({
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str
})
step_id="user",
data_schema=vol.Schema(
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
),
)
return await self._create_device(user_input[CONF_USERNAME], user_input[CONF_PASSWORD])
return await self._create_device(
user_input[CONF_USERNAME], user_input[CONF_PASSWORD]
)

async def async_step_import(self, user_input: dict) -> None:
"""Import a config entry."""
return await self._create_device(user_input[CONF_USERNAME], user_input[CONF_PASSWORD])
return await self._create_device(
user_input[CONF_USERNAME], user_input[CONF_PASSWORD]
)
1 change: 1 addition & 0 deletions custom_components/polestar_api/const.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Constants for the Polestar API integration."""

DOMAIN = "polestar_api"
TIMEOUT = 90

Expand Down
30 changes: 18 additions & 12 deletions custom_components/polestar_api/image.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Support Polestar image."""

from __future__ import annotations

from dataclasses import dataclass
import datetime
import logging
from dataclasses import dataclass
from typing import Final

from homeassistant.components.image import Image, ImageEntity, ImageEntityDescription
Expand All @@ -18,17 +19,17 @@

_LOGGER = logging.getLogger(__name__)


@dataclass
class PolestarImageDescriptionMixin:
"""A mixin class for image entities."""

query: str
field_name: str


@dataclass
class PolestarImageDescription(
ImageEntityDescription, PolestarImageDescriptionMixin
):
class PolestarImageDescription(ImageEntityDescription, PolestarImageDescriptionMixin):
"""A class that describes image entities."""


Expand All @@ -41,12 +42,12 @@ class PolestarImageDescription(
),
)


async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback) -> None:
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Polestar image entities based on a config entry."""
# get the device
# get the device
device: Polestar
device = hass.data[POLESTAR_API_DOMAIN][entry.entry_id]
await device.init()
Expand All @@ -60,6 +61,7 @@ async def async_setup_entry(
async_add_entities(images)
entity_platform.current_platform.get()


class PolestarImage(PolestarEntity, ImageEntity):
"""Representation of a Polestar image."""

Expand All @@ -77,17 +79,20 @@ def __init__(
self._device = device
# get the last 4 character of the id
unique_id = device.vin[-4:]
self.entity_id = f"{POLESTAR_API_DOMAIN}.'polestar_'.{unique_id}_{description.key}"
self.entity_id = (
f"{POLESTAR_API_DOMAIN}.'polestar_'.{unique_id}_{description.key}"
)
self._attr_unique_id = f"polestar_{unique_id}-{description.key}"
self.entity_description = description
self._attr_translation_key = f"polestar_{description.key}"
self._attr_image_last_updated = datetime.datetime.now()


@property
def image_url(self) -> str | None:
"""Return the image URL."""
return self._device.get_value(self.entity_description.query, self.entity_description.field_name)
return self._device.get_value(
self.entity_description.query, self.entity_description.field_name
)

async def async_load_image(self) -> Image | None:
"""Load an image."""
Expand All @@ -96,7 +101,8 @@ async def async_load_image(self) -> Image | None:

await self._device.async_update()
value = self._device.get_value(
self.entity_description.query, self.entity_description.field_name, True)
self.entity_description.query, self.entity_description.field_name, True
)

return Image(
content=value,
Expand Down
7 changes: 3 additions & 4 deletions custom_components/polestar_api/polestar.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
"""Polestar API for Polestar integration."""

from datetime import datetime, timedelta
import logging
from datetime import datetime, timedelta

import httpx
from urllib3 import disable_warnings

from homeassistant.core import HomeAssistant
from homeassistant.util.unit_system import METRIC_SYSTEM, UnitSystem
from urllib3 import disable_warnings

from .pypolestar.exception import PolestarApiException, PolestarAuthException
from .pypolestar.polestar import PolestarApi
Expand Down Expand Up @@ -120,7 +119,7 @@ def get_value(self, query: str, field_name: str, skip_cache: bool = False):
data = self.polestarApi.get_cache_data(query, field_name, skip_cache)
if data is None:
# if amp and voltage can be null, so we will return 0
if field_name in ('chargingCurrentAmps', 'chargingPowerWatts'):
if field_name in ("chargingCurrentAmps", "chargingPowerWatts"):
return 0
return
return data
1 change: 1 addition & 0 deletions custom_components/polestar_api/pypolestar/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
"""Python Polestar API."""

__version__ = "1.1.0"
2 changes: 1 addition & 1 deletion custom_components/polestar_api/pypolestar/auth.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from datetime import datetime, timedelta
import json
import logging
from datetime import datetime, timedelta

from homeassistant.helpers.httpx_client import httpx

Expand Down
1 change: 1 addition & 0 deletions custom_components/polestar_api/pypolestar/const.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Constants for Polestar."""

CACHE_TIME = 30

CAR_INFO_DATA = "getConsumerCarsV2"
Expand Down
2 changes: 2 additions & 0 deletions custom_components/polestar_api/pypolestar/exception.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Exceptions for Polestar API."""


class PolestarApiException(Exception):
"""Base class for exceptions in this module."""

Expand Down
15 changes: 7 additions & 8 deletions custom_components/polestar_api/pypolestar/polestar.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Asynchronous Python client for the Polestar API.""" ""
from datetime import datetime, timedelta
import logging
from datetime import datetime, timedelta

import httpx

Expand Down Expand Up @@ -198,13 +198,12 @@ def get_cache_data(self, query: str, field_name: str, skip_cache: bool = False):
if self.cache_data and self.cache_data.get(query):
cache_entry = self.cache_data[query]
data = cache_entry["data"]
if data is not None:
if (
skip_cache is True
or cache_entry["timestamp"] + timedelta(seconds=CACHE_TIME)
> datetime.now()
):
return self._get_field_name_value(field_name, data)
if data is not None and (
skip_cache is True
or cache_entry["timestamp"] + timedelta(seconds=CACHE_TIME)
> datetime.now()
):
return self._get_field_name_value(field_name, data)
return None

def _set_latest_call_code(self, url: str, code: int):
Expand Down
14 changes: 6 additions & 8 deletions custom_components/polestar_api/sensor.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Support for Polestar sensors."""

import logging
from contextlib import suppress
from dataclasses import dataclass
from datetime import datetime, timedelta
import logging
from typing import Final

from homeassistant.components.sensor import (
Expand Down Expand Up @@ -641,7 +642,7 @@ def state(self) -> StateType:
self._attr_native_value = self._sensor_data

# if GUI changed the unit, we need to convert the value
if self._sensor_data:
if self._sensor_data: # noqa
if self._sensor_option_unit_of_measurement is not None:
if self._sensor_option_unit_of_measurement in (
UnitOfLength.MILES,
Expand Down Expand Up @@ -716,21 +717,19 @@ def state(self) -> StateType:
return None

# only round value if native value is not None
if self._attr_native_value:
if self._attr_native_value: # noqa
# round the value
if self.entity_description.round_digits is not None:
if self.entity_description.round_digits is not None: # noqa
# if the value is integer, remove the decimal
if self.entity_description.round_digits == 0 and isinstance(
self._attr_native_value, int
):
self._attr_native_value = int(self._attr_native_value)
try:
with suppress(ValueError):
self._attr_native_value = round(
float(self._attr_native_value),
self.entity_description.round_digits,
)
except ValueError:
pass

return self._attr_native_value

Expand All @@ -753,7 +752,6 @@ async def async_update(self) -> None:
self._attr_native_value = value
self._sensor_data = value


except Exception:
_LOGGER.warning("Failed to update sensor async update")
self.device.polestarApi.next_update = datetime.now() + timedelta(seconds=60)
Loading

0 comments on commit 9855e61

Please sign in to comment.