Bump Tesla dependency teslajsonpy to 0.18.3 (#49939)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Alan Tse 2021-05-01 17:04:37 -07:00 committed by GitHub
parent 796f9cad1f
commit 3546ff2da2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 118 additions and 38 deletions

View file

@ -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
)

View file

@ -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

View file

@ -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"

View file

@ -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": [
{

View file

@ -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

View file

@ -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

View file

@ -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