mirror of
https://github.com/home-assistant/core
synced 2024-10-05 22:37:29 +00:00
Bump Tesla dependency teslajsonpy to 0.18.3 (#49939)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
796f9cad1f
commit
3546ff2da2
|
@ -1,9 +1,11 @@
|
|||
"""Support for Tesla cars."""
|
||||
import asyncio
|
||||
from collections import defaultdict
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import async_timeout
|
||||
import httpx
|
||||
from teslajsonpy import Controller as TeslaAPI
|
||||
from teslajsonpy.exceptions import IncompleteCredentials, TeslaException
|
||||
import voluptuous as vol
|
||||
|
@ -17,11 +19,13 @@ from homeassistant.const import (
|
|||
CONF_SCAN_INTERVAL,
|
||||
CONF_TOKEN,
|
||||
CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_CLOSE,
|
||||
HTTP_UNAUTHORIZED,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.httpx_client import SERVER_SOFTWARE, USER_AGENT
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
|
@ -31,6 +35,7 @@ from homeassistant.util import slugify
|
|||
|
||||
from .config_flow import CannotConnect, InvalidAuth, validate_input
|
||||
from .const import (
|
||||
CONF_EXPIRATION,
|
||||
CONF_WAKE_ON_START,
|
||||
DATA_LISTENER,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
|
@ -113,6 +118,7 @@ async def async_setup(hass, base_config):
|
|||
CONF_PASSWORD: password,
|
||||
CONF_ACCESS_TOKEN: info[CONF_ACCESS_TOKEN],
|
||||
CONF_TOKEN: info[CONF_TOKEN],
|
||||
CONF_EXPIRATION: info[CONF_EXPIRATION],
|
||||
},
|
||||
options={CONF_SCAN_INTERVAL: scan_interval},
|
||||
)
|
||||
|
@ -134,7 +140,7 @@ async def async_setup_entry(hass, config_entry):
|
|||
hass.data.setdefault(DOMAIN, {})
|
||||
config = config_entry.data
|
||||
# Because users can have multiple accounts, we always create a new session so they have separate cookies
|
||||
websession = aiohttp_client.async_create_clientsession(hass)
|
||||
async_client = httpx.AsyncClient(headers={USER_AGENT: SERVER_SOFTWARE})
|
||||
email = config_entry.title
|
||||
if email in hass.data[DOMAIN] and CONF_SCAN_INTERVAL in hass.data[DOMAIN][email]:
|
||||
scan_interval = hass.data[DOMAIN][email][CONF_SCAN_INTERVAL]
|
||||
|
@ -144,27 +150,45 @@ async def async_setup_entry(hass, config_entry):
|
|||
hass.data[DOMAIN].pop(email)
|
||||
try:
|
||||
controller = TeslaAPI(
|
||||
websession,
|
||||
async_client,
|
||||
email=config.get(CONF_USERNAME),
|
||||
password=config.get(CONF_PASSWORD),
|
||||
refresh_token=config[CONF_TOKEN],
|
||||
access_token=config[CONF_ACCESS_TOKEN],
|
||||
expiration=config.get(CONF_EXPIRATION, 0),
|
||||
update_interval=config_entry.options.get(
|
||||
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
|
||||
),
|
||||
)
|
||||
(refresh_token, access_token) = await controller.connect(
|
||||
result = await controller.connect(
|
||||
wake_if_asleep=config_entry.options.get(
|
||||
CONF_WAKE_ON_START, DEFAULT_WAKE_ON_START
|
||||
)
|
||||
)
|
||||
refresh_token = result["refresh_token"]
|
||||
access_token = result["access_token"]
|
||||
except IncompleteCredentials as ex:
|
||||
await async_client.aclose()
|
||||
raise ConfigEntryAuthFailed from ex
|
||||
except TeslaException as ex:
|
||||
await async_client.aclose()
|
||||
if ex.code == HTTP_UNAUTHORIZED:
|
||||
raise ConfigEntryAuthFailed from ex
|
||||
_LOGGER.error("Unable to communicate with Tesla API: %s", ex.message)
|
||||
return False
|
||||
|
||||
async def _async_close_client(*_):
|
||||
await async_client.aclose()
|
||||
|
||||
@callback
|
||||
def _async_create_close_task():
|
||||
asyncio.create_task(_async_close_client())
|
||||
|
||||
config_entry.async_on_unload(
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, _async_close_client)
|
||||
)
|
||||
config_entry.async_on_unload(_async_create_close_task)
|
||||
|
||||
_async_save_tokens(hass, config_entry, access_token, refresh_token)
|
||||
coordinator = TeslaDataUpdateCoordinator(
|
||||
hass, config_entry=config_entry, controller=controller
|
||||
|
@ -240,7 +264,9 @@ class TeslaDataUpdateCoordinator(DataUpdateCoordinator):
|
|||
async def _async_update_data(self):
|
||||
"""Fetch data from API endpoint."""
|
||||
if self.controller.is_token_refreshed():
|
||||
(refresh_token, access_token) = self.controller.get_tokens()
|
||||
result = self.controller.get_tokens()
|
||||
refresh_token = result["refresh_token"]
|
||||
access_token = result["access_token"]
|
||||
_async_save_tokens(
|
||||
self.hass, self.config_entry, access_token, refresh_token
|
||||
)
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
"""Tesla Config Flow."""
|
||||
import logging
|
||||
|
||||
import httpx
|
||||
from teslajsonpy import Controller as TeslaAPI, TeslaException
|
||||
from teslajsonpy.exceptions import IncompleteCredentials
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries, core, exceptions
|
||||
|
@ -14,9 +16,11 @@ from homeassistant.const import (
|
|||
HTTP_UNAUTHORIZED,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.httpx_client import SERVER_SOFTWARE, USER_AGENT
|
||||
|
||||
from .const import (
|
||||
CONF_EXPIRATION,
|
||||
CONF_WAKE_ON_START,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DEFAULT_WAKE_ON_START,
|
||||
|
@ -35,6 +39,7 @@ class TeslaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
def __init__(self):
|
||||
"""Initialize the tesla flow."""
|
||||
self.username = None
|
||||
self.reauth = False
|
||||
|
||||
async def async_step_import(self, import_config):
|
||||
"""Import a config entry from configuration.yaml."""
|
||||
|
@ -46,10 +51,7 @@ class TeslaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
|
||||
if user_input is not None:
|
||||
existing_entry = self._async_entry_for_username(user_input[CONF_USERNAME])
|
||||
if (
|
||||
existing_entry
|
||||
and existing_entry.data[CONF_PASSWORD] == user_input[CONF_PASSWORD]
|
||||
):
|
||||
if existing_entry and not self.reauth:
|
||||
return self.async_abort(reason="already_configured")
|
||||
|
||||
try:
|
||||
|
@ -81,6 +83,7 @@ class TeslaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
async def async_step_reauth(self, data):
|
||||
"""Handle configuration by re-auth."""
|
||||
self.username = data[CONF_USERNAME]
|
||||
self.reauth = True
|
||||
return await self.async_step_user()
|
||||
|
||||
@staticmethod
|
||||
|
@ -146,26 +149,32 @@ async def validate_input(hass: core.HomeAssistant, data):
|
|||
"""
|
||||
|
||||
config = {}
|
||||
websession = aiohttp_client.async_create_clientsession(hass)
|
||||
async_client = httpx.AsyncClient(headers={USER_AGENT: SERVER_SOFTWARE})
|
||||
|
||||
try:
|
||||
controller = TeslaAPI(
|
||||
websession,
|
||||
async_client,
|
||||
email=data[CONF_USERNAME],
|
||||
password=data[CONF_PASSWORD],
|
||||
update_interval=DEFAULT_SCAN_INTERVAL,
|
||||
)
|
||||
(config[CONF_TOKEN], config[CONF_ACCESS_TOKEN]) = await controller.connect(
|
||||
test_login=True
|
||||
)
|
||||
result = await controller.connect(test_login=True)
|
||||
config[CONF_TOKEN] = result["refresh_token"]
|
||||
config[CONF_ACCESS_TOKEN] = result["access_token"]
|
||||
config[CONF_EXPIRATION] = result[CONF_EXPIRATION]
|
||||
config[CONF_USERNAME] = data[CONF_USERNAME]
|
||||
config[CONF_PASSWORD] = data[CONF_PASSWORD]
|
||||
except IncompleteCredentials as ex:
|
||||
_LOGGER.error("Authentication error: %s %s", ex.message, ex)
|
||||
raise InvalidAuth() from ex
|
||||
except TeslaException as ex:
|
||||
if ex.code == HTTP_UNAUTHORIZED:
|
||||
_LOGGER.error("Invalid credentials: %s", ex)
|
||||
raise InvalidAuth() from ex
|
||||
_LOGGER.error("Unable to communicate with Tesla API: %s", ex)
|
||||
raise CannotConnect() from ex
|
||||
finally:
|
||||
await async_client.aclose()
|
||||
_LOGGER.debug("Credentials successfully connected to the Tesla API")
|
||||
return config
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Const file for Tesla cars."""
|
||||
CONF_EXPIRATION = "expiration"
|
||||
CONF_WAKE_ON_START = "enable_wake_on_start"
|
||||
DOMAIN = "tesla"
|
||||
DATA_LISTENER = "listener"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Tesla",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/tesla",
|
||||
"requirements": ["teslajsonpy==0.11.5"],
|
||||
"requirements": ["teslajsonpy==0.18.3"],
|
||||
"codeowners": ["@zabuldon", "@alandtse"],
|
||||
"dhcp": [
|
||||
{
|
||||
|
|
|
@ -2223,7 +2223,7 @@ temperusb==1.5.3
|
|||
tesla-powerwall==0.3.5
|
||||
|
||||
# homeassistant.components.tesla
|
||||
teslajsonpy==0.11.5
|
||||
teslajsonpy==0.18.3
|
||||
|
||||
# homeassistant.components.tensorflow
|
||||
# tf-models-official==2.3.0
|
||||
|
|
|
@ -1180,7 +1180,7 @@ tellduslive==0.10.11
|
|||
tesla-powerwall==0.3.5
|
||||
|
||||
# homeassistant.components.tesla
|
||||
teslajsonpy==0.11.5
|
||||
teslajsonpy==0.18.3
|
||||
|
||||
# homeassistant.components.toon
|
||||
toonapi==0.2.0
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
"""Test the Tesla config flow."""
|
||||
import datetime
|
||||
from unittest.mock import patch
|
||||
|
||||
from teslajsonpy import TeslaException
|
||||
from teslajsonpy.exceptions import IncompleteCredentials, TeslaException
|
||||
|
||||
from homeassistant import config_entries, data_entry_flow, setup
|
||||
from homeassistant.components.tesla.const import (
|
||||
CONF_EXPIRATION,
|
||||
CONF_WAKE_ON_START,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DEFAULT_WAKE_ON_START,
|
||||
|
@ -22,6 +24,12 @@ from homeassistant.const import (
|
|||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
TEST_USERNAME = "test-username"
|
||||
TEST_TOKEN = "test-token"
|
||||
TEST_PASSWORD = "test-password"
|
||||
TEST_ACCESS_TOKEN = "test-access-token"
|
||||
TEST_VALID_EXPIRATION = datetime.datetime.now().timestamp() * 2
|
||||
|
||||
|
||||
async def test_form(hass):
|
||||
"""Test we get the form."""
|
||||
|
@ -34,7 +42,11 @@ async def test_form(hass):
|
|||
|
||||
with patch(
|
||||
"homeassistant.components.tesla.config_flow.TeslaAPI.connect",
|
||||
return_value=("test-refresh-token", "test-access-token"),
|
||||
return_value={
|
||||
"refresh_token": TEST_TOKEN,
|
||||
CONF_ACCESS_TOKEN: TEST_ACCESS_TOKEN,
|
||||
CONF_EXPIRATION: TEST_VALID_EXPIRATION,
|
||||
},
|
||||
), patch(
|
||||
"homeassistant.components.tesla.async_setup", return_value=True
|
||||
) as mock_setup, patch(
|
||||
|
@ -50,8 +62,9 @@ async def test_form(hass):
|
|||
assert result2["data"] == {
|
||||
CONF_USERNAME: "test@email.com",
|
||||
CONF_PASSWORD: "test",
|
||||
CONF_TOKEN: "test-refresh-token",
|
||||
CONF_ACCESS_TOKEN: "test-access-token",
|
||||
CONF_TOKEN: TEST_TOKEN,
|
||||
CONF_ACCESS_TOKEN: TEST_ACCESS_TOKEN,
|
||||
CONF_EXPIRATION: TEST_VALID_EXPIRATION,
|
||||
}
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
@ -69,7 +82,26 @@ async def test_form_invalid_auth(hass):
|
|||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"},
|
||||
{CONF_USERNAME: TEST_USERNAME, CONF_PASSWORD: TEST_PASSWORD},
|
||||
)
|
||||
|
||||
assert result2["type"] == "form"
|
||||
assert result2["errors"] == {"base": "invalid_auth"}
|
||||
|
||||
|
||||
async def test_form_invalid_auth_incomplete_credentials(hass):
|
||||
"""Test we handle invalid auth with incomplete credentials."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.tesla.config_flow.TeslaAPI.connect",
|
||||
side_effect=IncompleteCredentials(401),
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_USERNAME: TEST_USERNAME, CONF_PASSWORD: TEST_PASSWORD},
|
||||
)
|
||||
|
||||
assert result2["type"] == "form"
|
||||
|
@ -88,7 +120,7 @@ async def test_form_cannot_connect(hass):
|
|||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_PASSWORD: "test-password", CONF_USERNAME: "test-username"},
|
||||
{CONF_PASSWORD: TEST_PASSWORD, CONF_USERNAME: TEST_USERNAME},
|
||||
)
|
||||
|
||||
assert result2["type"] == "form"
|
||||
|
@ -99,8 +131,8 @@ async def test_form_repeat_identifier(hass):
|
|||
"""Test we handle repeat identifiers."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="test-username",
|
||||
data={"username": "test-username", "password": "test-password"},
|
||||
title=TEST_USERNAME,
|
||||
data={"username": TEST_USERNAME, "password": TEST_PASSWORD},
|
||||
options=None,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
@ -110,11 +142,15 @@ async def test_form_repeat_identifier(hass):
|
|||
)
|
||||
with patch(
|
||||
"homeassistant.components.tesla.config_flow.TeslaAPI.connect",
|
||||
return_value=("test-refresh-token", "test-access-token"),
|
||||
return_value={
|
||||
"refresh_token": TEST_TOKEN,
|
||||
CONF_ACCESS_TOKEN: TEST_ACCESS_TOKEN,
|
||||
CONF_EXPIRATION: TEST_VALID_EXPIRATION,
|
||||
},
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"},
|
||||
{CONF_USERNAME: TEST_USERNAME, CONF_PASSWORD: TEST_PASSWORD},
|
||||
)
|
||||
|
||||
assert result2["type"] == "abort"
|
||||
|
@ -125,8 +161,8 @@ async def test_form_reauth(hass):
|
|||
"""Test we handle reauth."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="test-username",
|
||||
data={"username": "test-username", "password": "same"},
|
||||
title=TEST_USERNAME,
|
||||
data={"username": TEST_USERNAME, "password": "same"},
|
||||
options=None,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
@ -134,15 +170,19 @@ async def test_form_reauth(hass):
|
|||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_REAUTH},
|
||||
data={"username": "test-username"},
|
||||
data={"username": TEST_USERNAME},
|
||||
)
|
||||
with patch(
|
||||
"homeassistant.components.tesla.config_flow.TeslaAPI.connect",
|
||||
return_value=("test-refresh-token", "test-access-token"),
|
||||
return_value={
|
||||
"refresh_token": TEST_TOKEN,
|
||||
CONF_ACCESS_TOKEN: TEST_ACCESS_TOKEN,
|
||||
CONF_EXPIRATION: TEST_VALID_EXPIRATION,
|
||||
},
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_USERNAME: "test-username", CONF_PASSWORD: "new-password"},
|
||||
{CONF_USERNAME: TEST_USERNAME, CONF_PASSWORD: "new-password"},
|
||||
)
|
||||
|
||||
assert result2["type"] == "abort"
|
||||
|
@ -154,17 +194,21 @@ async def test_import(hass):
|
|||
|
||||
with patch(
|
||||
"homeassistant.components.tesla.config_flow.TeslaAPI.connect",
|
||||
return_value=("test-refresh-token", "test-access-token"),
|
||||
return_value={
|
||||
"refresh_token": TEST_TOKEN,
|
||||
CONF_ACCESS_TOKEN: TEST_ACCESS_TOKEN,
|
||||
CONF_EXPIRATION: TEST_VALID_EXPIRATION,
|
||||
},
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={CONF_PASSWORD: "test-password", CONF_USERNAME: "test-username"},
|
||||
data={CONF_PASSWORD: TEST_PASSWORD, CONF_USERNAME: TEST_USERNAME},
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == "test-username"
|
||||
assert result["data"][CONF_ACCESS_TOKEN] == "test-access-token"
|
||||
assert result["data"][CONF_TOKEN] == "test-refresh-token"
|
||||
assert result["title"] == TEST_USERNAME
|
||||
assert result["data"][CONF_ACCESS_TOKEN] == TEST_ACCESS_TOKEN
|
||||
assert result["data"][CONF_TOKEN] == TEST_TOKEN
|
||||
assert result["description_placeholders"] is None
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue