-
-
Notifications
You must be signed in to change notification settings - Fork 28.4k
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 sleep to prevent API throttling #15331
Conversation
@@ -218,3 +225,5 @@ def update(self): | |||
from_currency=self._from_currency, to_currency=self._to_currency) | |||
_LOGGER.debug("Received new data for forex %s - %s", | |||
self._from_currency, self._to_currency) | |||
_LOGGER.debug("Sleep %s seconds to prevent API throttling...", 10) | |||
time.sleep(10) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
undefined name 'time'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this in the second commit.
@@ -162,6 +167,8 @@ def update(self): | |||
all_values, _ = self._timeseries.get_intraday(self._symbol) | |||
self.values = next(iter(all_values.values())) | |||
_LOGGER.debug("Received new values for symbol %s", self._symbol) | |||
_LOGGER.debug("Sleep %s seconds to prevent API throttling...", 10) | |||
time.sleep(10) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
undefined name 'time'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this in the second commit.
@@ -101,6 +104,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): | |||
_LOGGER.debug("Configuring forex %s - %s", from_cur, to_cur) | |||
forex.get_currency_exchange_rate( | |||
from_currency=from_cur, to_currency=to_cur) | |||
_LOGGER.debug("Sleep %s seconds to prevent API throttling...", 10) | |||
time.sleep(10) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
undefined name 'time'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this in the second commit.
for symbol in symbols: | ||
try: | ||
_LOGGER.debug("Configuring timeseries for symbols: %s", | ||
symbol[CONF_SYMBOL]) | ||
timeseries.get_intraday(symbol[CONF_SYMBOL]) | ||
except ValueError: | ||
_LOGGER.debug("Sleep %s seconds to prevent API throttling...", 10) | ||
time.sleep(10) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
undefined name 'time'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this in the second commit.
cc @fabaff |
dev = [] | ||
|
||
timeseries = TimeSeries(key=api_key) | ||
for symbol in symbols: | ||
try: | ||
_LOGGER.debug("Configuring timeseries for symbols: %s", | ||
symbol[CONF_SYMBOL]) | ||
timeseries.get_intraday(symbol[CONF_SYMBOL]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it necessary to do I/O already here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was in the original source already. So I wouldn't know. I only move these lines below dev = []
in line with the rest of the code further down.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I don't know either, but having a sleep inside the setup
is not acceptable (as it will make everything else in homeassistant to wait, too). Maybe it's fine to do an initial fetching here, and then just throttle the updates 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fully understand that a sleep isn't the most elegant way to do this, but I can't think of another way taking into account how this API is working (accepting only an unknown but very lownumber of calls per second).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi,
i wrote #15258 which also contains sleep statements, although in a more random fashion.
In order to ban the sleeps from the setup method, i think it is necessary to have only one query against the api. The current implementation runs a query for every configured stock/currencyexchange. When you simply want to check if the api key is valid, one query is enough.
Or not running queries in Setup() at all, because later in the update() methods one get's crash-log-entries anyway when the key is bad.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we don't make api calls at setup, a sensor will always be created regardless if a valid symbol is used or not. How would you reflect those errors on updates? Keep the state of the sensor at 'None' and send a 'critical' error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be the negative effect, that there can be a certain amount error messages, if you have many stocks to query. On the other side, if the api key is incorrect, then it should be corrected asap. If a symbol is incorrect, i also find it ok, if a system "screams", because it also needs to be fixed. The error messages an be switched off by simply maintaining a proper configuration.
And yes, returning None will make the symbols disappear and then the user will definively have a deeper look on what's wrong.
@@ -162,6 +168,8 @@ def update(self): | |||
all_values, _ = self._timeseries.get_intraday(self._symbol) | |||
self.values = next(iter(all_values.values())) | |||
_LOGGER.debug("Received new values for symbol %s", self._symbol) | |||
_LOGGER.debug("Sleep %s seconds to prevent API throttling...", 10) | |||
time.sleep(10) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of doing I/O on setup_platforms
it should be done inside update
. Instead of sleeping you should use homeassistant.util.Throttle
decorator (grep for Throttle
on the codebase to see examples).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well. It (also) needs to be done in some way during setup of the platforms. The API is being hit in the setup_platforms
as well as in the update
.
Can a Throttle
be used for setup_platforms
, too?
I will look into the Throttle for the update and add a commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setup_platform
is only called once when the platform is initialized, so there's no need to throttle that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, that's exactly my point. The reason I started thinking about a PR in the first place was the platform setup. If you have more that a few ( 1 or 2) symbols, then the current platform setup will show errors because of throttling. The setup will call the API for each symbol. See line 90:
timeseries.get_intraday(symbol[CONF_SYMBOL])
5, 6 or 10 of these calls and the API will complain (and those sensors will not be created at all).
@rytilahti Thanks for the quick review. I'm a fairly novice (Python) developer, and tried to apply a quick fix with my limited knowledge. |
I have been doing some thinking and testing. The |
for symbol in symbols: | ||
try: | ||
_LOGGER.debug("Configuring timeseries for symbols: %s", | ||
symbol[CONF_SYMBOL]) | ||
timeseries.get_intraday(symbol[CONF_SYMBOL]) | ||
except ValueError: | ||
_LOGGER.debug("Sleep %s seconds to prevent API throttling...", 10) | ||
time.sleep(10) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
time.sleep
is never allowed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was already aware of that. The basic approach is
- Get rid of API calls at platform setup
- Use Throttle for the update() function.
@balloob Only question is if the throttle will work for 1 specific sensor or across sensors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@metbril the proper way to do this is to have the main component polling & updating some shared data-structure with the polled contents. The sensors themselves are set to require no polling, and are informed by the main component when the values are updated. Unfortunately I don't know what's a simple example using this structure, only that xiaomi_aqara is doing it like that. I hope someone else will chime in on that!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Platforms can also be based around a main data structure (client) class that interacts with an api. This structure is set up when the platform is first set up and stored in hass.data. Subsequent platform set ups should check for existing client in hass.data. The client instance pulls all data from the api, in a single request, in the client update method which is throttled, and stores the data in an instance attribute. Entities call the client update method in their update methods and access the client data attribute when needed.
I really appreciate all your good suggestions, but this is getting way over my head. I am not an experienced coder so I will run into a lot of issues before (if) ever completing this PR. Unless someone else wants to continue working on this, I think I will close the PR. Thing is, the component is not working properly when used with more then a few sensors. And it's the only API I know that also supports European stocks. |
Description:
Related issue (if applicable): fixes #15271
This PR builds in some sleep time to prevent the API from throttling. Although the API has no daily, weekly or monthly limits, it limits the number of calls in a smaller (minutely?) timeframe. This prevents creating more than a few sensors at startup and/or updating those at the required interval.
Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.github.io#<home-assistant.github.io PR number goes here> n/a
Example entry for
configuration.yaml
(if applicable):Checklist:
tox
. Your PR cannot be merged unless tests passIf user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
REQUIREMENTS
variable (example).requirements_all.txt
by runningscript/gen_requirements_all.py
..coveragerc
.If the code does not interact with devices: