Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add user picture to fyta #140934

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions homeassistant/components/fyta/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util.dt import async_get_time_zone

from .const import CONF_EXPIRATION
from .const import CONF_EXPIRATION, CONF_USER_IMAGE
from .coordinator import FytaConfigEntry, FytaCoordinator

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -52,9 +52,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: FytaConfigEntry) -> bool

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

entry.async_on_unload(entry.add_update_listener(update_listener))

return True


async def update_listener(hass: HomeAssistant, entry: FytaConfigEntry) -> None:
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)


async def async_unload_entry(hass: HomeAssistant, entry: FytaConfigEntry) -> bool:
"""Unload Fyta entity."""

Expand All @@ -72,6 +79,13 @@ async def async_migrate_entry(
return False

if config_entry.version == 1:
if config_entry.minor_version == 2:
hass.config_entries.async_update_entry(
config_entry,
options={CONF_USER_IMAGE: False},
minor_version=3,
version=1,
)
if config_entry.minor_version < 2:
new = {**config_entry.data}
fyta = FytaConnector(
Expand All @@ -84,7 +98,11 @@ async def async_migrate_entry(
new[CONF_EXPIRATION] = credentials.expiration.isoformat()

hass.config_entries.async_update_entry(
config_entry, data=new, minor_version=2, version=1
config_entry,
data=new,
options={CONF_USER_IMAGE: False},
minor_version=3,
version=1,
)

_LOGGER.debug(
Expand Down
39 changes: 36 additions & 3 deletions homeassistant/components/fyta/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@
from fyta_cli.fyta_models import Credentials
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import callback
from homeassistant.helpers.selector import (
TextSelector,
TextSelectorConfig,
TextSelectorType,
)

from .const import CONF_EXPIRATION, DOMAIN
from .const import CONF_EXPIRATION, CONF_USER_IMAGE, DOMAIN
from .coordinator import FytaConfigEntry

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -51,7 +53,15 @@ class FytaConfigFlow(ConfigFlow, domain=DOMAIN):

credentials: Credentials
VERSION = 1
MINOR_VERSION = 2
MINOR_VERSION = 3

@staticmethod
@callback
def async_get_options_flow(
config_entry: FytaConfigEntry,
) -> FytaOptionsFlowHandler:
"""Get the options flow for this handler."""
return FytaOptionsFlowHandler()

async def async_auth(self, user_input: Mapping[str, Any]) -> dict[str, str]:
"""Reusable Auth Helper."""
Expand Down Expand Up @@ -126,3 +136,26 @@ async def async_step_reauth_confirm(
data_schema=data_schema,
errors=errors,
)


class FytaOptionsFlowHandler(OptionsFlow):
"""Handle a options flow for fyta."""

async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Manage the options."""
if user_input is not None:
return self.async_create_entry(data=user_input)

return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required(
CONF_USER_IMAGE,
default=self.config_entry.options[CONF_USER_IMAGE],
): bool,
}
),
)
1 change: 1 addition & 0 deletions homeassistant/components/fyta/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@

DOMAIN = "fyta"
CONF_EXPIRATION = "expiration"
CONF_USER_IMAGE = "user_image"
54 changes: 47 additions & 7 deletions homeassistant/components/fyta/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,23 @@
from __future__ import annotations

from datetime import datetime

from homeassistant.components.image import ImageEntity, ImageEntityDescription
import logging

from homeassistant.components.image import (
Image,
ImageEntity,
ImageEntityDescription,
valid_image_content_type,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .coordinator import FytaConfigEntry, FytaCoordinator
from .entity import FytaPlantEntity

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(
hass: HomeAssistant,
Expand Down Expand Up @@ -54,12 +62,44 @@
ImageEntity.__init__(self, coordinator.hass)

self._attr_name = None
self._user_image: bool = coordinator.config_entry.options["user_image"]

async def async_image(self) -> bytes | None:
"""Return bytes of image."""

if self._user_image:
if self._cached_image is None:
response = await self.coordinator.fyta.get_plant_image(
self.plant.user_picture_path
)
_LOGGER.debug("Response of downloading user image: %s", response)
if response is None:
_LOGGER.debug(
"%s: Error getting new image from %s",
self.entity_id,
self.plant.user_picture_path,
)
return None

content_type, raw_image = response
self._cached_image = Image(
valid_image_content_type(content_type), raw_image
)

return self._cached_image.content

return await ImageEntity.async_image(self)

Check warning on line 91 in homeassistant/components/fyta/image.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/fyta/image.py#L91

Added line #L91 was not covered by tests

@property
def image_url(self) -> str:
"""Return the image_url for this sensor."""
image = self.plant.plant_origin_path
if image != self._attr_image_url:
self._attr_image_last_updated = datetime.now()
"""Return the image_url for this plant."""
url = (
self.plant.user_picture_path
if self._user_image
else self.plant.plant_origin_path
)

return image
if url != self._attr_image_url:
self._cached_image = None
self._attr_image_last_updated = datetime.now()
return url
5 changes: 1 addition & 4 deletions homeassistant/components/fyta/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@ rules:
test-coverage: done
integration-owner: done
docs-installation-parameters: done
docs-configuration-parameters:
status: exempt
comment: |
No options flow.
docs-configuration-parameters: done

# Gold
entity-translations: done
Expand Down
14 changes: 14 additions & 0 deletions homeassistant/components/fyta/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
}
},
"options": {
"step": {
"init": {
"title": "Configure FYTA",
"description": "Configure the FYTA integration",
"data": {
"user_image": "User image"
},
"data_description": {
"user_image": "Use the user image insteas of the generic plant image."
}
}
}
},
"entity": {
"binary_sensor": {
"notification_light": {
Expand Down
9 changes: 7 additions & 2 deletions tests/components/fyta/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
from fyta_cli.fyta_models import Credentials, Plant
import pytest

from homeassistant.components.fyta.const import CONF_EXPIRATION, DOMAIN as FYTA_DOMAIN
from homeassistant.components.fyta.const import (
CONF_EXPIRATION,
CONF_USER_IMAGE,
DOMAIN as FYTA_DOMAIN,
)
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_PASSWORD, CONF_USERNAME

from .const import ACCESS_TOKEN, EXPIRATION, PASSWORD, USERNAME
Expand All @@ -27,7 +31,8 @@ def mock_config_entry() -> MockConfigEntry:
CONF_ACCESS_TOKEN: ACCESS_TOKEN,
CONF_EXPIRATION: EXPIRATION,
},
minor_version=2,
options={CONF_USER_IMAGE: False},
minor_version=3,
entry_id="ce5f5431554d101905d31797e1232da8",
)

Expand Down
2 changes: 1 addition & 1 deletion tests/components/fyta/fixtures/plant_status1_update.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"sw_version": "1.0",
"status": 1,
"online": true,
"origin_path": "http://www.plant_picture.com/user_picture",
"origin_path": "http://www.plant_picture.com/user_picture1",
"ph": null,
"plant_id": 0,
"plant_origin_path": "http://www.plant_picture.com/picture1",
Expand Down
3 changes: 2 additions & 1 deletion tests/components/fyta/snapshots/test_diagnostics.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
}),
'domain': 'fyta',
'entry_id': 'ce5f5431554d101905d31797e1232da8',
'minor_version': 2,
'minor_version': 3,
'options': dict({
'user_image': False,
}),
'pref_disable_new_entities': False,
'pref_disable_polling': False,
Expand Down
6 changes: 6 additions & 0 deletions tests/components/fyta/snapshots/test_image.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,9 @@
'state': 'unknown',
})
# ---
# name: test_update_user_image
None
# ---
# name: test_update_user_image.1
b'd'
# ---
36 changes: 34 additions & 2 deletions tests/components/fyta/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@
import pytest

from homeassistant import config_entries
from homeassistant.components.fyta.const import CONF_EXPIRATION, DOMAIN
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_PASSWORD, CONF_USERNAME
from homeassistant.components.fyta.const import CONF_EXPIRATION, CONF_USER_IMAGE, DOMAIN
from homeassistant.const import (
CONF_ACCESS_TOKEN,
CONF_PASSWORD,
CONF_USERNAME,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo

from . import setup_platform
from .const import ACCESS_TOKEN, EXPIRATION, PASSWORD, USERNAME

from tests.common import MockConfigEntry
Expand Down Expand Up @@ -222,3 +228,29 @@ async def test_dhcp_discovery(
assert result["errors"] == {}

await user_step(hass, result["flow_id"], mock_setup_entry)


async def test_options_flow(
hass: HomeAssistant,
mock_fyta_connector: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test options flow works correctly."""
await setup_platform(hass, mock_config_entry, [Platform.SENSOR])

result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)

assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "init"

result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
CONF_USER_IMAGE: True,
},
)

assert result["type"] is FlowResultType.CREATE_ENTRY
assert mock_config_entry.options == {
CONF_USER_IMAGE: True,
}
Loading