Skip to content

Commit

Permalink
Merge remote-tracking branch
Browse files Browse the repository at this point in the history
  • Loading branch information
iprak committed May 31, 2021
2 parents 59e4f28 + c176d42 commit f072735
Show file tree
Hide file tree
Showing 5 changed files with 695 additions and 484 deletions.
87 changes: 63 additions & 24 deletions custom_components/yahoofinance/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,33 @@ def parse_symbol_data(symbol_data):

return data

@staticmethod
def fix_conversion_symbol(symbol: str, symbol_data: any) -> str:
""" Fix the conversion symbol from data."""

if symbol is None or symbol == "" or not symbol.endswith("=X"):
return symbol

# Data analysis showed that data for conversion symbol has 'shortName': 'USD/EUR'
short_name = symbol_data["shortName"] or ""
from_to = short_name.split("/")
if len(from_to) != 2:
return symbol

from_currency = from_to[0]
to_currency = from_to[1]
if from_currency == "" or to_currency == "":
return symbol

conversion_symbol = f"{from_currency}{to_currency}=X"

if conversion_symbol != symbol:
_LOGGER.info(
"Conversion symbol updated to %s from %s", conversion_symbol, symbol
)

return conversion_symbol

def __init__(self, symbols, hass, update_interval) -> None:
"""Initialize."""
self._symbols = symbols
Expand Down Expand Up @@ -137,7 +164,6 @@ async def get_json(self):
response = await self.websession.get(url)
json = await response.json()

_LOGGER.debug("Data = %s", json)
return json

async def _async_update(self):
Expand Down Expand Up @@ -169,33 +195,43 @@ async def _async_update(self):
if result is None:
raise UpdateFailed("Data invalid, 'result' is None")

# Using current data if available. If returned data is missing some symbols then we might be
# able to use previous data.
data = self.data or {}
(error_encountered, data) = self.process_json_result(result)

pos = 0
symbols_count = len(self._symbols)
if error_encountered:
_LOGGER.info("Data = %s", result)
else:
_LOGGER.debug("Data = %s", result)

# We should receive data matching the symbols requested
_LOGGER.info("All symbols updated")
return data

for symbol_data in result:
symbol_received = symbol_data["symbol"]
symbol_requested = None
def process_json_result(self, result):
"""Processes json result and returns (error status, updated data)."""

if pos < symbols_count:
symbol_requested = self._symbols[pos]
pos += 1
# Using current data if available. If returned data is missing then we might be
# able to use previous data.
data = self.data or {}

if symbol_requested != symbol_received:
_LOGGER.warning(
"Requested data for %s and received %s",
symbol_requested,
symbol_received,
)
symbols = self._symbols.copy()
error_encountered = False

# Sometimes data for USDEUR=X just contains EUR=X, giving preference to the requested
# symbol instead of symbol from data.
symbol = symbol_requested or symbol_received
for symbol_data in result:
symbol = symbol_data["symbol"]

if symbol in symbols:
symbols.remove(symbol)
else:
# Sometimes data for USDEUR=X just contains EUR=X, try to fix such
# symbols. The source of truth is the symbol in the data since data
# pieces could be out of order.
fixed_symbol = self.fix_conversion_symbol(symbol, symbol_data)

if fixed_symbol in symbols:
symbols.remove(fixed_symbol)
symbol = fixed_symbol
else:
_LOGGER.warning("Received %s not in symbol list", symbol)
error_encountered = True

data[symbol] = self.parse_symbol_data(symbol_data)

Expand All @@ -205,5 +241,8 @@ async def _async_update(self):
data[symbol][DATA_REGULAR_MARKET_PRICE],
)

_LOGGER.info("All symbols updated")
return data
if len(symbols) > 0:
_LOGGER.warning("No data received for %s", symbols)
error_encountered = True

return (error_encountered, data)
2 changes: 1 addition & 1 deletion custom_components/yahoofinance/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"@iprak"
],
"issue_tracker": "https://github.com/iprak/yahoofinance/issues",
"version": "1.0.10"
"version": "1.0.11"
}
29 changes: 23 additions & 6 deletions custom_components/yahoofinance/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""

import logging
from timeit import default_timer as timer

from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.helpers.entity import Entity, async_generate_entity_id
Expand Down Expand Up @@ -68,6 +69,7 @@ class YahooFinanceSensor(Entity):
_short_name = None
_target_currency = None
_original_currency = None
_last_available_timer = None

def __init__(self, hass, coordinator, symbol_definition, domain_config) -> None:
"""Initialize the sensor."""
Expand Down Expand Up @@ -191,7 +193,13 @@ def safe_convert(value, conversion):
return value
return value * conversion

def _get_original_currency(self, symbol_data):
def _update_original_currency(self, symbol_data) -> bool:
"""Update the original currency."""

# Symbol currency does not change so calculate it only once
if self._original_currency is not None:
return

# Prefer currency over financialCurrency, for foreign symbols financialCurrency
# can represent the remote currency. But financialCurrency can also be None.
financial_currency = symbol_data[DATA_FINANCIAL_CURRENCY]
Expand All @@ -204,10 +212,9 @@ def _get_original_currency(self, symbol_data):
financial_currency,
)

currency = currency or financial_currency or DEFAULT_CURRENCY
return currency
self._original_currency = currency or financial_currency or DEFAULT_CURRENCY

def _update_data(self) -> None:
def _update_properties(self) -> None:
"""Update local fields."""

data = self._coordinator.data
Expand All @@ -220,7 +227,7 @@ def _update_data(self) -> None:
_LOGGER.debug("%s Symbol data is None", self._symbol)
return

self._original_currency = self._get_original_currency(symbol_data)
self._update_original_currency(symbol_data)
conversion = self._get_target_currency_conversion()

self._short_name = symbol_data[DATA_SHORT_NAME]
Expand Down Expand Up @@ -299,7 +306,17 @@ def _calc_trending_state(self):
@property
def available(self) -> bool:
"""Return if entity is available."""
self._update_data()

current_timer = timer()

# Limit data update if available was invoked within 400 ms.
# This matched the slow entity reporting done in Entity.
if (self._last_available_timer is None) or (
(current_timer - self._last_available_timer) > 0.4
):
self._update_properties()
self._last_available_timer = current_timer

return self._coordinator.last_update_success

async def async_added_to_hass(self) -> None:
Expand Down
Loading

0 comments on commit f072735

Please sign in to comment.